orionis 0.512.0__py3-none-any.whl → 0.514.0__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.
- orionis/console/base/command.py +1 -1
- orionis/console/base/scheduler.py +1 -1
- orionis/console/base/scheduler_event_listener.py +9 -10
- orionis/console/commands/scheduler_list.py +3 -3
- orionis/console/commands/scheduler_work.py +19 -16
- orionis/console/contracts/schedule.py +210 -44
- orionis/console/contracts/schedule_event_listener.py +0 -38
- orionis/console/contracts/scheduler.py +140 -0
- orionis/console/core/reactor.py +1 -1
- orionis/console/entities/job_modified.py +1 -2
- orionis/console/enums/event.py +19 -11
- orionis/console/enums/listener.py +44 -56
- orionis/console/tasks/event.py +31 -13
- orionis/console/tasks/schedule.py +600 -376
- orionis/foundation/application.py +1 -1
- orionis/metadata/framework.py +1 -1
- {orionis-0.512.0.dist-info → orionis-0.514.0.dist-info}/METADATA +1 -1
- {orionis-0.512.0.dist-info → orionis-0.514.0.dist-info}/RECORD +23 -24
- orionis/console/base/contracts/__init__.py +0 -0
- orionis/console/base/contracts/scheduler.py +0 -38
- /orionis/console/{base/contracts → contracts}/command.py +0 -0
- {orionis-0.512.0.dist-info → orionis-0.514.0.dist-info}/WHEEL +0 -0
- {orionis-0.512.0.dist-info → orionis-0.514.0.dist-info}/licenses/LICENCE +0 -0
- {orionis-0.512.0.dist-info → orionis-0.514.0.dist-info}/top_level.txt +0 -0
- {orionis-0.512.0.dist-info → orionis-0.514.0.dist-info}/zip-safe +0 -0
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
from datetime import datetime
|
|
3
3
|
import logging
|
|
4
|
-
from typing import Dict, List, Optional
|
|
4
|
+
from typing import Dict, List, Optional, Union
|
|
5
5
|
import pytz
|
|
6
6
|
from apscheduler.events import (
|
|
7
|
-
|
|
8
|
-
EVENT_JOB_EXECUTED,
|
|
9
|
-
EVENT_JOB_MISSED,
|
|
10
|
-
EVENT_JOB_SUBMITTED,
|
|
7
|
+
EVENT_SCHEDULER_STARTED,
|
|
11
8
|
EVENT_SCHEDULER_PAUSED,
|
|
12
9
|
EVENT_SCHEDULER_RESUMED,
|
|
13
10
|
EVENT_SCHEDULER_SHUTDOWN,
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
EVENT_JOB_ERROR,
|
|
12
|
+
EVENT_JOB_SUBMITTED,
|
|
13
|
+
EVENT_JOB_EXECUTED,
|
|
14
|
+
EVENT_JOB_MISSED,
|
|
15
|
+
EVENT_JOB_MAX_INSTANCES,
|
|
16
|
+
EVENT_JOB_MODIFIED,
|
|
17
|
+
EVENT_JOB_REMOVED
|
|
16
18
|
)
|
|
17
19
|
from apscheduler.schedulers.asyncio import AsyncIOScheduler as APSAsyncIOScheduler
|
|
18
20
|
from rich.console import Console
|
|
@@ -20,17 +22,22 @@ from rich.panel import Panel
|
|
|
20
22
|
from rich.text import Text
|
|
21
23
|
from orionis.console.contracts.reactor import IReactor
|
|
22
24
|
from orionis.console.contracts.schedule import ISchedule
|
|
25
|
+
from orionis.console.contracts.schedule_event_listener import IScheduleEventListener
|
|
23
26
|
from orionis.console.entities.job_error import JobError
|
|
24
27
|
from orionis.console.entities.job_executed import JobExecuted
|
|
25
28
|
from orionis.console.entities.job_max_instances import JobMaxInstances
|
|
26
29
|
from orionis.console.entities.job_missed import JobMissed
|
|
30
|
+
from orionis.console.entities.job_modified import JobModified
|
|
31
|
+
from orionis.console.entities.job_removed import JobRemoved
|
|
27
32
|
from orionis.console.entities.job_submitted import JobSubmitted
|
|
28
33
|
from orionis.console.entities.scheduler_paused import SchedulerPaused
|
|
29
34
|
from orionis.console.entities.scheduler_resumed import SchedulerResumed
|
|
30
35
|
from orionis.console.entities.scheduler_shutdown import SchedulerShutdown
|
|
31
36
|
from orionis.console.entities.scheduler_started import SchedulerStarted
|
|
32
37
|
from orionis.console.enums.listener import ListeningEvent
|
|
38
|
+
from orionis.console.enums.event import Event as EventEntity
|
|
33
39
|
from orionis.console.exceptions import CLIOrionisRuntimeError
|
|
40
|
+
from orionis.console.exceptions.cli_orionis_value_error import CLIOrionisValueError
|
|
34
41
|
from orionis.console.output.contracts.console import IConsole
|
|
35
42
|
from orionis.console.tasks.event import Event
|
|
36
43
|
from orionis.foundation.contracts.application import IApplication
|
|
@@ -100,7 +107,7 @@ class Scheduler(ISchedule):
|
|
|
100
107
|
self.__events: Dict[str, Event] = {}
|
|
101
108
|
|
|
102
109
|
# Initialize the jobs list to keep track of all scheduled jobs.
|
|
103
|
-
self.__jobs: List[
|
|
110
|
+
self.__jobs: List[EventEntity] = []
|
|
104
111
|
|
|
105
112
|
# Initialize the listeners dictionary to manage event listeners.
|
|
106
113
|
self.__listeners: Dict[str, callable] = {}
|
|
@@ -122,12 +129,163 @@ class Scheduler(ISchedule):
|
|
|
122
129
|
"""
|
|
123
130
|
|
|
124
131
|
# Get the current time in the configured timezone
|
|
125
|
-
|
|
126
|
-
|
|
132
|
+
tz = pytz.timezone(self.__app.config("app.timezone", "UTC"))
|
|
133
|
+
now = datetime.now(tz)
|
|
134
|
+
|
|
135
|
+
# Format the current time as a string
|
|
136
|
+
return now.strftime("%Y-%m-%d %H:%M:%S")
|
|
137
|
+
|
|
138
|
+
def __getCommands(
|
|
139
|
+
self
|
|
140
|
+
) -> dict:
|
|
141
|
+
"""
|
|
142
|
+
Retrieve available commands from the reactor and return them as a dictionary.
|
|
143
|
+
|
|
144
|
+
This method queries the reactor for all available jobs/commands, extracting their
|
|
145
|
+
signatures and descriptions. The result is a dictionary where each key is the command
|
|
146
|
+
signature and the value is another dictionary containing the command's signature and
|
|
147
|
+
its description.
|
|
148
|
+
|
|
149
|
+
Returns
|
|
150
|
+
-------
|
|
151
|
+
dict
|
|
152
|
+
A dictionary mapping command signatures to their details. Each value is a dictionary
|
|
153
|
+
with 'signature' and 'description' keys.
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
# Initialize the commands dictionary
|
|
157
|
+
commands = {}
|
|
158
|
+
|
|
159
|
+
# Iterate over all jobs provided by the reactor's info method
|
|
160
|
+
for job in self.__reactor.info():
|
|
161
|
+
|
|
162
|
+
# Store each job's signature and description in the commands dictionary
|
|
163
|
+
commands[job['signature']] = {
|
|
164
|
+
'signature': job['signature'],
|
|
165
|
+
'description': job.get('description', '')
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
# Return the commands dictionary
|
|
169
|
+
return commands
|
|
170
|
+
|
|
171
|
+
def __isAvailable(
|
|
172
|
+
self,
|
|
173
|
+
signature: str
|
|
174
|
+
) -> bool:
|
|
175
|
+
"""
|
|
176
|
+
Check if a command with the given signature is available.
|
|
177
|
+
|
|
178
|
+
This method iterates through the available commands and determines
|
|
179
|
+
whether the provided signature matches any registered command.
|
|
180
|
+
|
|
181
|
+
Parameters
|
|
182
|
+
----------
|
|
183
|
+
signature : str
|
|
184
|
+
The signature of the command to check for availability.
|
|
185
|
+
|
|
186
|
+
Returns
|
|
187
|
+
-------
|
|
188
|
+
bool
|
|
189
|
+
True if the command with the specified signature exists and is available,
|
|
190
|
+
False otherwise.
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
# Iterate through all available command signatures
|
|
194
|
+
for command in self.__available_commands.keys():
|
|
195
|
+
|
|
196
|
+
# Return True if the signature matches an available command
|
|
197
|
+
if command == signature:
|
|
198
|
+
return True
|
|
199
|
+
|
|
200
|
+
# Return False if the signature is not found among available commands
|
|
201
|
+
return False
|
|
202
|
+
|
|
203
|
+
def __getDescription(
|
|
204
|
+
self,
|
|
205
|
+
signature: str
|
|
206
|
+
) -> Optional[str]:
|
|
207
|
+
"""
|
|
208
|
+
Retrieve the description of a command given its signature.
|
|
209
|
+
|
|
210
|
+
This method looks up the available commands dictionary and returns the description
|
|
211
|
+
associated with the provided command signature. If the signature does not exist,
|
|
212
|
+
it returns None.
|
|
213
|
+
|
|
214
|
+
Parameters
|
|
215
|
+
----------
|
|
216
|
+
signature : str
|
|
217
|
+
The unique signature identifying the command.
|
|
218
|
+
|
|
219
|
+
Returns
|
|
220
|
+
-------
|
|
221
|
+
Optional[str]
|
|
222
|
+
The description of the command if found; otherwise, None.
|
|
223
|
+
"""
|
|
224
|
+
|
|
225
|
+
# Attempt to retrieve the command entry from the available commands dictionary
|
|
226
|
+
command_entry = self.__available_commands.get(signature)
|
|
227
|
+
|
|
228
|
+
# Return the description if the command exists, otherwise return None
|
|
229
|
+
return command_entry['description'] if command_entry else None
|
|
230
|
+
|
|
231
|
+
def command(
|
|
232
|
+
self,
|
|
233
|
+
signature: str,
|
|
234
|
+
args: Optional[List[str]] = None
|
|
235
|
+
) -> 'Event':
|
|
236
|
+
"""
|
|
237
|
+
Prepare an Event instance for a given command signature and its arguments.
|
|
238
|
+
|
|
239
|
+
This method validates the provided command signature and arguments, ensuring
|
|
240
|
+
that the command exists among the registered commands and that the arguments
|
|
241
|
+
are in the correct format. If validation passes, it creates and returns an
|
|
242
|
+
Event object representing the scheduled command, including its signature,
|
|
243
|
+
arguments, and description.
|
|
244
|
+
|
|
245
|
+
Parameters
|
|
246
|
+
----------
|
|
247
|
+
signature : str
|
|
248
|
+
The unique signature identifying the command to be scheduled. Must be a non-empty string.
|
|
249
|
+
args : Optional[List[str]], optional
|
|
250
|
+
A list of string arguments to be passed to the command. Defaults to None.
|
|
251
|
+
|
|
252
|
+
Returns
|
|
253
|
+
-------
|
|
254
|
+
Event
|
|
255
|
+
An Event instance containing the command signature, arguments, and its description.
|
|
256
|
+
|
|
257
|
+
Raises
|
|
258
|
+
------
|
|
259
|
+
ValueError
|
|
260
|
+
If the command signature is not a non-empty string, if the arguments are not a list
|
|
261
|
+
of strings or None, or if the command does not exist among the registered commands.
|
|
262
|
+
"""
|
|
263
|
+
|
|
264
|
+
# Prevent adding new commands while the scheduler is running
|
|
265
|
+
if self.__scheduler.running:
|
|
266
|
+
raise CLIOrionisRuntimeError("Cannot add new commands while the scheduler is running.")
|
|
267
|
+
|
|
268
|
+
# Validate that the command signature is a non-empty string
|
|
269
|
+
if not isinstance(signature, str) or not signature.strip():
|
|
270
|
+
raise CLIOrionisValueError("Command signature must be a non-empty string.")
|
|
271
|
+
|
|
272
|
+
# Ensure that arguments are either a list of strings or None
|
|
273
|
+
if args is not None and not isinstance(args, list):
|
|
274
|
+
raise CLIOrionisValueError("Arguments must be a list of strings or None.")
|
|
275
|
+
|
|
276
|
+
# Check if the command is available in the registered commands
|
|
277
|
+
if not self.__isAvailable(signature):
|
|
278
|
+
raise CLIOrionisValueError(f"The command '{signature}' is not available or does not exist.")
|
|
279
|
+
|
|
280
|
+
# Store the command and its arguments for scheduling
|
|
281
|
+
self.__events[signature] = Event(
|
|
282
|
+
signature=signature,
|
|
283
|
+
args=args or [],
|
|
284
|
+
purpose=self.__getDescription(signature)
|
|
127
285
|
)
|
|
128
286
|
|
|
129
|
-
# Return the
|
|
130
|
-
return
|
|
287
|
+
# Return the Event instance for further scheduling configuration
|
|
288
|
+
return self.__events[signature]
|
|
131
289
|
|
|
132
290
|
def __suscribeListeners(
|
|
133
291
|
self
|
|
@@ -150,148 +308,186 @@ class Scheduler(ISchedule):
|
|
|
150
308
|
This method does not return any value. It configures event listeners on the scheduler.
|
|
151
309
|
"""
|
|
152
310
|
|
|
153
|
-
# Add a listener for the scheduler started event
|
|
154
311
|
self.__scheduler.add_listener(self.__startedListener, EVENT_SCHEDULER_STARTED)
|
|
155
|
-
|
|
156
|
-
|
|
312
|
+
self.__scheduler.add_listener(self.__pausedListener, EVENT_SCHEDULER_PAUSED)
|
|
313
|
+
self.__scheduler.add_listener(self.__resumedListener, EVENT_SCHEDULER_RESUMED)
|
|
157
314
|
self.__scheduler.add_listener(self.__shutdownListener, EVENT_SCHEDULER_SHUTDOWN)
|
|
315
|
+
self.__scheduler.add_listener(self.__errorListener, EVENT_JOB_ERROR)
|
|
316
|
+
self.__scheduler.add_listener(self.__submittedListener, EVENT_JOB_SUBMITTED)
|
|
317
|
+
self.__scheduler.add_listener(self.__executedListener, EVENT_JOB_EXECUTED)
|
|
318
|
+
self.__scheduler.add_listener(self.__missedListener, EVENT_JOB_MISSED)
|
|
319
|
+
self.__scheduler.add_listener(self.__maxInstancesListener, EVENT_JOB_MAX_INSTANCES)
|
|
320
|
+
self.__scheduler.add_listener(self.__modifiedListener, EVENT_JOB_MODIFIED)
|
|
321
|
+
self.__scheduler.add_listener(self.__removedListener, EVENT_JOB_REMOVED)
|
|
158
322
|
|
|
159
|
-
|
|
160
|
-
self
|
|
323
|
+
def __globalCallableListener(
|
|
324
|
+
self,
|
|
325
|
+
event_data: Optional[Union[SchedulerStarted, SchedulerPaused, SchedulerResumed, SchedulerShutdown, JobError]],
|
|
326
|
+
listening_vent: ListeningEvent
|
|
327
|
+
) -> None:
|
|
328
|
+
"""
|
|
329
|
+
Invoke registered listeners for global scheduler events.
|
|
161
330
|
|
|
162
|
-
|
|
163
|
-
|
|
331
|
+
This method handles global scheduler events such as when the scheduler starts, pauses, resumes,
|
|
332
|
+
or shuts down. It checks if a listener is registered for the specified event and invokes it if callable.
|
|
333
|
+
The listener can be either a coroutine or a regular function.
|
|
164
334
|
|
|
165
|
-
|
|
166
|
-
|
|
335
|
+
Parameters
|
|
336
|
+
----------
|
|
337
|
+
event_data : Optional[Union[SchedulerStarted, SchedulerPaused, SchedulerResumed, SchedulerShutdown, JobError]]
|
|
338
|
+
The event data associated with the global scheduler event. This can include details about the event,
|
|
339
|
+
such as its type and context. If no specific data is available, this parameter can be None.
|
|
340
|
+
listening_vent : ListeningEvent
|
|
341
|
+
An instance of the ListeningEvent enum representing the global scheduler event to handle.
|
|
167
342
|
|
|
168
|
-
|
|
169
|
-
|
|
343
|
+
Returns
|
|
344
|
+
-------
|
|
345
|
+
None
|
|
346
|
+
This method does not return any value. It invokes the registered listener for the specified event,
|
|
347
|
+
if one exists.
|
|
170
348
|
|
|
171
|
-
|
|
172
|
-
|
|
349
|
+
Raises
|
|
350
|
+
------
|
|
351
|
+
CLIOrionisValueError
|
|
352
|
+
If the provided `listening_vent` is not an instance of ListeningEvent.
|
|
353
|
+
"""
|
|
173
354
|
|
|
174
|
-
#
|
|
175
|
-
|
|
355
|
+
# Validate that the provided event is an instance of ListeningEvent
|
|
356
|
+
if not isinstance(listening_vent, ListeningEvent):
|
|
357
|
+
raise CLIOrionisValueError("The event must be an instance of ListeningEvent.")
|
|
176
358
|
|
|
177
|
-
#
|
|
178
|
-
|
|
359
|
+
# Retrieve the global identifier for the event from the ListeningEvent enum
|
|
360
|
+
scheduler_event = listening_vent.value
|
|
179
361
|
|
|
180
|
-
|
|
362
|
+
# Check if a listener is registered for the specified event
|
|
363
|
+
if scheduler_event in self.__listeners:
|
|
364
|
+
listener = self.__listeners[scheduler_event]
|
|
365
|
+
|
|
366
|
+
# Ensure the listener is callable before invoking it
|
|
367
|
+
if callable(listener):
|
|
368
|
+
# If the listener is a coroutine, schedule it as an asyncio task
|
|
369
|
+
if asyncio.iscoroutinefunction(listener):
|
|
370
|
+
asyncio.create_task(listener(event_data, self))
|
|
371
|
+
# Otherwise, invoke the listener directly as a regular function
|
|
372
|
+
else:
|
|
373
|
+
listener(event_data, self)
|
|
374
|
+
|
|
375
|
+
def __taskCallableListener(
|
|
181
376
|
self,
|
|
182
|
-
|
|
377
|
+
event_data: Optional[Union[JobError, JobExecuted, JobSubmitted, JobMissed, JobMaxInstances]],
|
|
378
|
+
listening_vent: ListeningEvent
|
|
183
379
|
) -> None:
|
|
184
380
|
"""
|
|
185
|
-
|
|
381
|
+
Invoke registered listeners for specific task/job events.
|
|
186
382
|
|
|
187
|
-
This method
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
383
|
+
This method handles task/job-specific events such as job errors, executions, submissions,
|
|
384
|
+
missed jobs, and max instance violations. It checks if a listener is registered for the
|
|
385
|
+
specific job ID associated with the event and invokes the appropriate method on the listener
|
|
386
|
+
if callable. The listener can be either a coroutine or a regular function.
|
|
191
387
|
|
|
192
388
|
Parameters
|
|
193
389
|
----------
|
|
194
|
-
|
|
195
|
-
|
|
390
|
+
event_data : Optional[Union[JobError, JobExecuted, JobSubmitted, JobMissed, JobMaxInstances]]
|
|
391
|
+
The event data associated with the task/job event. This includes details about the job,
|
|
392
|
+
such as its ID, exception (if any), and other context. If no specific data is available,
|
|
393
|
+
this parameter can be None.
|
|
394
|
+
listening_vent : ListeningEvent
|
|
395
|
+
An instance of the ListeningEvent enum representing the task/job event to handle.
|
|
196
396
|
|
|
197
397
|
Returns
|
|
198
398
|
-------
|
|
199
399
|
None
|
|
200
|
-
This method does not return any value. It
|
|
201
|
-
|
|
202
|
-
|
|
400
|
+
This method does not return any value. It invokes the registered listener for the
|
|
401
|
+
specified job event, if one exists.
|
|
402
|
+
|
|
403
|
+
Raises
|
|
404
|
+
------
|
|
405
|
+
CLIOrionisValueError
|
|
406
|
+
If the provided `listening_vent` is not an instance of ListeningEvent.
|
|
203
407
|
"""
|
|
204
408
|
|
|
205
|
-
#
|
|
206
|
-
|
|
409
|
+
# Validate that the provided event is an instance of ListeningEvent
|
|
410
|
+
if not isinstance(listening_vent, ListeningEvent):
|
|
411
|
+
raise CLIOrionisValueError("The event must be an instance of ListeningEvent.")
|
|
207
412
|
|
|
208
|
-
#
|
|
209
|
-
|
|
413
|
+
# Retrieve the global identifier for the event from the ListeningEvent enum
|
|
414
|
+
scheduler_event = listening_vent.value
|
|
210
415
|
|
|
211
|
-
#
|
|
212
|
-
|
|
213
|
-
if self.__app.config('app.debug', False):
|
|
214
|
-
self.__rich_console.line()
|
|
215
|
-
panel_content = Text.assemble(
|
|
216
|
-
(" Orionis Scheduler Worker ", "bold white on green"), # Header text with styling
|
|
217
|
-
("\n\n", ""), # Add spacing
|
|
218
|
-
("The scheduled tasks worker has started successfully.\n", "white"), # Main message
|
|
219
|
-
(f"Started at: {now}\n", "dim"), # Display the start time in dim text
|
|
220
|
-
("To stop the worker, press ", "white"), # Instruction text
|
|
221
|
-
("Ctrl+C", "bold yellow"), # Highlight the key combination
|
|
222
|
-
(".", "white") # End the instruction
|
|
223
|
-
)
|
|
224
|
-
|
|
225
|
-
# Display the message in a styled panel
|
|
226
|
-
self.__rich_console.print(
|
|
227
|
-
Panel(panel_content, border_style="green", padding=(1, 2))
|
|
228
|
-
)
|
|
229
|
-
|
|
230
|
-
# Add another blank line for better formatting
|
|
231
|
-
self.__rich_console.line()
|
|
232
|
-
|
|
233
|
-
# Retrieve the global identifier for the scheduler started event
|
|
234
|
-
scheduler_started = ListeningEvent.SCHEDULER_STARTED.value
|
|
416
|
+
# Check if a listener is registered for the specific job ID in the event data
|
|
417
|
+
if event_data.job_id in self.__listeners:
|
|
235
418
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
listener = self.__listeners[scheduler_started]
|
|
419
|
+
# Retrieve the listener for the specific job ID
|
|
420
|
+
listener = self.__listeners[event_data.job_id]
|
|
239
421
|
|
|
240
|
-
#
|
|
241
|
-
if
|
|
422
|
+
# Check if the listener is an instance of IScheduleEventListener
|
|
423
|
+
if isinstance(listener, IScheduleEventListener):
|
|
242
424
|
|
|
243
|
-
#
|
|
244
|
-
listener(
|
|
425
|
+
# Check if the listener has a method corresponding to the event type
|
|
426
|
+
if hasattr(listener, scheduler_event) and callable(getattr(listener, scheduler_event)):
|
|
427
|
+
listener_method = getattr(listener, scheduler_event)
|
|
245
428
|
|
|
246
|
-
|
|
429
|
+
# Invoke the listener method, handling both coroutine and regular functions
|
|
430
|
+
if asyncio.iscoroutinefunction(listener_method):
|
|
431
|
+
# Schedule the coroutine listener method as an asyncio task
|
|
432
|
+
asyncio.create_task(listener_method(event_data, self))
|
|
433
|
+
else:
|
|
434
|
+
# Call the regular listener method directly
|
|
435
|
+
listener_method(event_data, self)
|
|
436
|
+
|
|
437
|
+
def __startedListener(
|
|
247
438
|
self,
|
|
248
|
-
event:
|
|
439
|
+
event: SchedulerStarted
|
|
249
440
|
) -> None:
|
|
250
441
|
"""
|
|
251
|
-
Handle the scheduler
|
|
442
|
+
Handle the scheduler started event for logging and invoking registered listeners.
|
|
252
443
|
|
|
253
|
-
This method is triggered when the scheduler
|
|
254
|
-
message indicating that the scheduler has
|
|
444
|
+
This method is triggered when the scheduler starts. It logs an informational
|
|
445
|
+
message indicating that the scheduler has started successfully and displays
|
|
255
446
|
a formatted message on the rich console. If a listener is registered for the
|
|
256
|
-
scheduler
|
|
447
|
+
scheduler started event, it invokes the listener with the event details.
|
|
257
448
|
|
|
258
449
|
Parameters
|
|
259
450
|
----------
|
|
260
|
-
event :
|
|
261
|
-
An event object containing details about the scheduler
|
|
451
|
+
event : SchedulerStarted
|
|
452
|
+
An event object containing details about the scheduler start event.
|
|
262
453
|
|
|
263
454
|
Returns
|
|
264
455
|
-------
|
|
265
456
|
None
|
|
266
457
|
This method does not return any value. It performs logging, displays
|
|
267
458
|
a message on the console, and invokes any registered listener for the
|
|
268
|
-
scheduler
|
|
459
|
+
scheduler started event.
|
|
269
460
|
"""
|
|
270
461
|
|
|
271
462
|
# Get the current time in the configured timezone
|
|
272
463
|
now = self.__getCurrentTime()
|
|
273
464
|
|
|
274
|
-
#
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
# Log an informational message indicating that the scheduler has shut down
|
|
278
|
-
self.__logger.info(message)
|
|
465
|
+
# Log an informational message indicating that the scheduler has started
|
|
466
|
+
self.__logger.info(f"Orionis Scheduler started successfully at {now}.")
|
|
279
467
|
|
|
280
|
-
# Display a
|
|
281
|
-
|
|
282
|
-
|
|
468
|
+
# Display a start message for the scheduler worker on the rich console
|
|
469
|
+
# Add a blank line for better formatting
|
|
470
|
+
self.__rich_console.line()
|
|
471
|
+
panel_content = Text.assemble(
|
|
472
|
+
(" Orionis Scheduler Worker ", "bold white on green"), # Header text with styling
|
|
473
|
+
("\n\n", ""), # Add spacing
|
|
474
|
+
("The scheduled tasks worker has started successfully.\n", "white"), # Main message
|
|
475
|
+
(f"Started at: {now}\n", "dim"), # Display the start time in dim text
|
|
476
|
+
("To stop the worker, press ", "white"), # Instruction text
|
|
477
|
+
("Ctrl+C", "bold yellow"), # Highlight the key combination
|
|
478
|
+
(".", "white") # End the instruction
|
|
479
|
+
)
|
|
283
480
|
|
|
284
|
-
#
|
|
285
|
-
|
|
481
|
+
# Display the message in a styled panel
|
|
482
|
+
self.__rich_console.print(
|
|
483
|
+
Panel(panel_content, border_style="green", padding=(1, 2))
|
|
484
|
+
)
|
|
286
485
|
|
|
287
|
-
#
|
|
288
|
-
|
|
289
|
-
listener = self.__listeners[scheduler_shutdown]
|
|
486
|
+
# Add another blank line for better formatting
|
|
487
|
+
self.__rich_console.line()
|
|
290
488
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
# Invoke the registered listener with the event details
|
|
294
|
-
listener(event)
|
|
489
|
+
# Check if a listener is registered for the scheduler started event
|
|
490
|
+
self.__globalCallableListener(event, ListeningEvent.SCHEDULER_STARTED)
|
|
295
491
|
|
|
296
492
|
def __pausedListener(
|
|
297
493
|
self,
|
|
@@ -331,17 +527,8 @@ class Scheduler(ISchedule):
|
|
|
331
527
|
if self.__app.config('app.debug', False):
|
|
332
528
|
self.__console.info(message)
|
|
333
529
|
|
|
334
|
-
#
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
# Check if a listener is registered for the scheduler paused event
|
|
338
|
-
if scheduler_paused in self.__listeners:
|
|
339
|
-
listener = self.__listeners[scheduler_paused]
|
|
340
|
-
|
|
341
|
-
# Ensure the listener is callable before invoking it
|
|
342
|
-
if callable(listener):
|
|
343
|
-
# Invoke the registered listener with the event details
|
|
344
|
-
listener(event)
|
|
530
|
+
# Check if a listener is registered for the scheduler started event
|
|
531
|
+
self.__globalCallableListener(event, ListeningEvent.SCHEDULER_PAUSED)
|
|
345
532
|
|
|
346
533
|
def __resumedListener(
|
|
347
534
|
self,
|
|
@@ -381,17 +568,90 @@ class Scheduler(ISchedule):
|
|
|
381
568
|
if self.__app.config('app.debug', False):
|
|
382
569
|
self.__console.info(message)
|
|
383
570
|
|
|
384
|
-
#
|
|
385
|
-
|
|
571
|
+
# Check if a listener is registered for the scheduler started event
|
|
572
|
+
self.__globalCallableListener(event, ListeningEvent.SCHEDULER_RESUMED)
|
|
386
573
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
574
|
+
def __shutdownListener(
|
|
575
|
+
self,
|
|
576
|
+
event: SchedulerShutdown
|
|
577
|
+
) -> None:
|
|
578
|
+
"""
|
|
579
|
+
Handle the scheduler shutdown event for logging and invoking registered listeners.
|
|
390
580
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
581
|
+
This method is triggered when the scheduler shuts down. It logs an informational
|
|
582
|
+
message indicating that the scheduler has shut down successfully and displays
|
|
583
|
+
a formatted message on the rich console. If a listener is registered for the
|
|
584
|
+
scheduler shutdown event, it invokes the listener with the event details.
|
|
585
|
+
|
|
586
|
+
Parameters
|
|
587
|
+
----------
|
|
588
|
+
event : SchedulerShutdown
|
|
589
|
+
An event object containing details about the scheduler shutdown event.
|
|
590
|
+
|
|
591
|
+
Returns
|
|
592
|
+
-------
|
|
593
|
+
None
|
|
594
|
+
This method does not return any value. It performs logging, displays
|
|
595
|
+
a message on the console, and invokes any registered listener for the
|
|
596
|
+
scheduler shutdown event.
|
|
597
|
+
"""
|
|
598
|
+
|
|
599
|
+
# Get the current time in the configured timezone
|
|
600
|
+
now = self.__getCurrentTime()
|
|
601
|
+
|
|
602
|
+
# Create a shutdown message
|
|
603
|
+
message = f"Orionis Scheduler shut down successfully at {now}."
|
|
604
|
+
|
|
605
|
+
# Log an informational message indicating that the scheduler has shut down
|
|
606
|
+
self.__logger.info(message)
|
|
607
|
+
|
|
608
|
+
# Display a shutdown message for the scheduler worker on console
|
|
609
|
+
if self.__app.config('app.debug', False):
|
|
610
|
+
self.__console.info(message)
|
|
611
|
+
|
|
612
|
+
# Check if a listener is registered for the scheduler started event
|
|
613
|
+
self.__globalCallableListener(event, ListeningEvent.SCHEDULER_SHUTDOWN)
|
|
614
|
+
|
|
615
|
+
def __errorListener(
|
|
616
|
+
self,
|
|
617
|
+
event: JobError
|
|
618
|
+
) -> None:
|
|
619
|
+
"""
|
|
620
|
+
Handle job error events for logging and error reporting.
|
|
621
|
+
|
|
622
|
+
This method is triggered when a job execution results in an error. It logs an error
|
|
623
|
+
message indicating the job ID and the exception raised. If the application is in
|
|
624
|
+
debug mode, it also reports the error using the error reporter. Additionally, if a
|
|
625
|
+
listener is registered for the errored job, it invokes the listener with the event details.
|
|
626
|
+
|
|
627
|
+
Parameters
|
|
628
|
+
----------
|
|
629
|
+
event : JobError
|
|
630
|
+
An instance of the JobError event containing details about the errored job,
|
|
631
|
+
including its ID and the exception raised.
|
|
632
|
+
|
|
633
|
+
Returns
|
|
634
|
+
-------
|
|
635
|
+
None
|
|
636
|
+
This method does not return any value. It performs logging, error reporting,
|
|
637
|
+
and listener invocation for the job error event.
|
|
638
|
+
"""
|
|
639
|
+
|
|
640
|
+
# Create an error message
|
|
641
|
+
message = f"Task {event.job_id} raised an exception: {event.exception}"
|
|
642
|
+
|
|
643
|
+
# Log an error message indicating that the job raised an exception
|
|
644
|
+
self.__logger.error(message)
|
|
645
|
+
|
|
646
|
+
# If the application is in debug mode, display a message on the console
|
|
647
|
+
if self.__app.config('app.debug', False):
|
|
648
|
+
self.__console.error(message)
|
|
649
|
+
|
|
650
|
+
# If a listener is registered for this job ID, invoke the listener with the event details
|
|
651
|
+
self.__taskCallableListener(event, ListeningEvent.JOB_ON_FAILURE)
|
|
652
|
+
|
|
653
|
+
# Check if a listener is registered for the scheduler started event
|
|
654
|
+
self.__globalCallableListener(event, ListeningEvent.SCHEDULER_ERROR)
|
|
395
655
|
|
|
396
656
|
def __submittedListener(
|
|
397
657
|
self,
|
|
@@ -429,14 +689,7 @@ class Scheduler(ISchedule):
|
|
|
429
689
|
self.__console.info(message)
|
|
430
690
|
|
|
431
691
|
# If a listener is registered for this job ID, invoke the listener with the event details
|
|
432
|
-
|
|
433
|
-
listener = self.__listeners[event.job_id]
|
|
434
|
-
|
|
435
|
-
# Ensure the listener is callable before invoking it
|
|
436
|
-
if callable(listener):
|
|
437
|
-
|
|
438
|
-
# Invoke the registered listener with the event details
|
|
439
|
-
listener(event)
|
|
692
|
+
self.__taskCallableListener(event, ListeningEvent.JOB_BEFORE)
|
|
440
693
|
|
|
441
694
|
def __executedListener(
|
|
442
695
|
self,
|
|
@@ -475,14 +728,7 @@ class Scheduler(ISchedule):
|
|
|
475
728
|
self.__console.info(message)
|
|
476
729
|
|
|
477
730
|
# If a listener is registered for this job ID, invoke the listener with the event details
|
|
478
|
-
|
|
479
|
-
listener = self.__listeners[event.job_id]
|
|
480
|
-
|
|
481
|
-
# Ensure the listener is callable before invoking it
|
|
482
|
-
if callable(listener):
|
|
483
|
-
|
|
484
|
-
# Invoke the registered listener with the event details
|
|
485
|
-
listener(event)
|
|
731
|
+
self.__taskCallableListener(event, ListeningEvent.JOB_AFTER)
|
|
486
732
|
|
|
487
733
|
def __missedListener(
|
|
488
734
|
self,
|
|
@@ -521,59 +767,7 @@ class Scheduler(ISchedule):
|
|
|
521
767
|
self.__console.warning(message)
|
|
522
768
|
|
|
523
769
|
# If a listener is registered for this job ID, invoke the listener with the event details
|
|
524
|
-
|
|
525
|
-
listener = self.__listeners[event.job_id]
|
|
526
|
-
|
|
527
|
-
# Ensure the listener is callable before invoking it
|
|
528
|
-
if callable(listener):
|
|
529
|
-
|
|
530
|
-
# Invoke the registered listener with the event details
|
|
531
|
-
listener(event)
|
|
532
|
-
|
|
533
|
-
def __errorListener(
|
|
534
|
-
self,
|
|
535
|
-
event: JobError
|
|
536
|
-
) -> None:
|
|
537
|
-
"""
|
|
538
|
-
Handle job error events for logging and error reporting.
|
|
539
|
-
|
|
540
|
-
This method is triggered when a job execution results in an error. It logs an error
|
|
541
|
-
message indicating the job ID and the exception raised. If the application is in
|
|
542
|
-
debug mode, it also reports the error using the error reporter. Additionally, if a
|
|
543
|
-
listener is registered for the errored job, it invokes the listener with the event details.
|
|
544
|
-
|
|
545
|
-
Parameters
|
|
546
|
-
----------
|
|
547
|
-
event : JobError
|
|
548
|
-
An instance of the JobError event containing details about the errored job,
|
|
549
|
-
including its ID and the exception raised.
|
|
550
|
-
|
|
551
|
-
Returns
|
|
552
|
-
-------
|
|
553
|
-
None
|
|
554
|
-
This method does not return any value. It performs logging, error reporting,
|
|
555
|
-
and listener invocation for the job error event.
|
|
556
|
-
"""
|
|
557
|
-
|
|
558
|
-
# Create an error message
|
|
559
|
-
message = f"Task {event.job_id} raised an exception: {event.exception}"
|
|
560
|
-
|
|
561
|
-
# Log an error message indicating that the job raised an exception
|
|
562
|
-
self.__logger.error(message)
|
|
563
|
-
|
|
564
|
-
# If the application is in debug mode, display a message on the console
|
|
565
|
-
if self.__app.config('app.debug', False):
|
|
566
|
-
self.__console.error(message)
|
|
567
|
-
|
|
568
|
-
# If a listener is registered for this job ID, invoke the listener with the event details
|
|
569
|
-
if event.job_id in self.__listeners:
|
|
570
|
-
listener = self.__listeners[event.job_id]
|
|
571
|
-
|
|
572
|
-
# Ensure the listener is callable before invoking it
|
|
573
|
-
if callable(listener):
|
|
574
|
-
|
|
575
|
-
# Invoke the registered listener with the event details
|
|
576
|
-
listener(event)
|
|
770
|
+
self.__taskCallableListener(event, ListeningEvent.JOB_ON_MISSED)
|
|
577
771
|
|
|
578
772
|
def __maxInstancesListener(
|
|
579
773
|
self,
|
|
@@ -612,107 +806,87 @@ class Scheduler(ISchedule):
|
|
|
612
806
|
self.__console.error(message)
|
|
613
807
|
|
|
614
808
|
# If a listener is registered for this job ID, invoke the listener with the event details
|
|
615
|
-
|
|
616
|
-
listener = self.__listeners[event.job_id]
|
|
617
|
-
|
|
618
|
-
# Ensure the listener is callable before invoking it
|
|
619
|
-
if callable(listener):
|
|
620
|
-
|
|
621
|
-
# Invoke the registered listener with the event details
|
|
622
|
-
listener(event)
|
|
623
|
-
|
|
624
|
-
def __getCommands(
|
|
625
|
-
self
|
|
626
|
-
) -> dict:
|
|
627
|
-
"""
|
|
628
|
-
Retrieve available commands from the reactor and return them as a dictionary.
|
|
629
|
-
|
|
630
|
-
This method queries the reactor for all available jobs/commands, extracting their
|
|
631
|
-
signatures and descriptions. The result is a dictionary where each key is the command
|
|
632
|
-
signature and the value is another dictionary containing the command's signature and
|
|
633
|
-
its description.
|
|
634
|
-
|
|
635
|
-
Returns
|
|
636
|
-
-------
|
|
637
|
-
dict
|
|
638
|
-
A dictionary mapping command signatures to their details. Each value is a dictionary
|
|
639
|
-
with 'signature' and 'description' keys.
|
|
640
|
-
"""
|
|
641
|
-
|
|
642
|
-
# Initialize the commands dictionary
|
|
643
|
-
commands = {}
|
|
809
|
+
self.__taskCallableListener(event, ListeningEvent.JOB_ON_MAXINSTANCES)
|
|
644
810
|
|
|
645
|
-
|
|
646
|
-
for job in self.__reactor.info():
|
|
647
|
-
|
|
648
|
-
# Store each job's signature and description in the commands dictionary
|
|
649
|
-
commands[job['signature']] = {
|
|
650
|
-
'signature': job['signature'],
|
|
651
|
-
'description': job.get('description', '')
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
# Return the commands dictionary
|
|
655
|
-
return commands
|
|
656
|
-
|
|
657
|
-
def __isAvailable(
|
|
811
|
+
def __modifiedListener(
|
|
658
812
|
self,
|
|
659
|
-
|
|
660
|
-
) ->
|
|
813
|
+
event: JobModified
|
|
814
|
+
) -> None:
|
|
661
815
|
"""
|
|
662
|
-
|
|
816
|
+
Handle job modified events for logging and error reporting.
|
|
663
817
|
|
|
664
|
-
This method
|
|
665
|
-
|
|
818
|
+
This method is triggered when a job is modified. It logs an informational
|
|
819
|
+
message indicating that the job has been modified successfully. If the application
|
|
820
|
+
is in debug mode, it also displays a message on the console. Additionally, if a
|
|
821
|
+
listener is registered for the modified job, it invokes the listener with the
|
|
822
|
+
event details.
|
|
666
823
|
|
|
667
824
|
Parameters
|
|
668
825
|
----------
|
|
669
|
-
|
|
670
|
-
|
|
826
|
+
event : JobModified
|
|
827
|
+
An instance of the JobModified event containing details about the modified job,
|
|
828
|
+
including its ID and other relevant information.
|
|
671
829
|
|
|
672
830
|
Returns
|
|
673
831
|
-------
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
832
|
+
None
|
|
833
|
+
This method does not return any value. It performs logging, error reporting,
|
|
834
|
+
and listener invocation for the job modified event.
|
|
677
835
|
"""
|
|
678
836
|
|
|
679
|
-
#
|
|
680
|
-
|
|
837
|
+
# Create a modified message
|
|
838
|
+
message = f"Task {event.job_id} has been modified."
|
|
681
839
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
return True
|
|
840
|
+
# Log an informational message indicating that the job has been modified
|
|
841
|
+
self.__logger.info(message)
|
|
685
842
|
|
|
686
|
-
#
|
|
687
|
-
|
|
843
|
+
# If the application is in debug mode, display a message on the console
|
|
844
|
+
if self.__app.config('app.debug', False):
|
|
845
|
+
self.__console.info(message)
|
|
688
846
|
|
|
689
|
-
|
|
847
|
+
# If a listener is registered for this job ID, invoke the listener with the event details
|
|
848
|
+
if event.next_run_time is None:
|
|
849
|
+
self.__taskCallableListener(event, ListeningEvent.JOB_ON_PAUSED)
|
|
850
|
+
else:
|
|
851
|
+
self.__taskCallableListener(event, ListeningEvent.JOB_ON_RESUMED)
|
|
852
|
+
|
|
853
|
+
def __removedListener(
|
|
690
854
|
self,
|
|
691
|
-
|
|
692
|
-
) ->
|
|
855
|
+
event: JobRemoved
|
|
856
|
+
) -> None:
|
|
693
857
|
"""
|
|
694
|
-
|
|
858
|
+
Handle job removal events for logging and invoking registered listeners.
|
|
695
859
|
|
|
696
|
-
This method
|
|
697
|
-
|
|
698
|
-
it
|
|
860
|
+
This method is triggered when a job is removed from the scheduler. It logs an informational
|
|
861
|
+
message indicating that the job has been removed successfully. If the application is in debug
|
|
862
|
+
mode, it displays a message on the console. Additionally, if a listener is registered for the
|
|
863
|
+
removed job, it invokes the listener with the event details.
|
|
699
864
|
|
|
700
865
|
Parameters
|
|
701
866
|
----------
|
|
702
|
-
|
|
703
|
-
|
|
867
|
+
event : JobRemoved
|
|
868
|
+
An instance of the JobRemoved event containing details about the removed job,
|
|
869
|
+
including its ID and other relevant information.
|
|
704
870
|
|
|
705
871
|
Returns
|
|
706
872
|
-------
|
|
707
|
-
|
|
708
|
-
|
|
873
|
+
None
|
|
874
|
+
This method does not return any value. It performs logging and invokes any registered
|
|
875
|
+
listener for the job removal event.
|
|
709
876
|
"""
|
|
710
877
|
|
|
711
|
-
#
|
|
712
|
-
|
|
878
|
+
# Create a message indicating that the job has been removed
|
|
879
|
+
message = f"Task {event.job_id} has been removed."
|
|
713
880
|
|
|
714
|
-
#
|
|
715
|
-
|
|
881
|
+
# Log the removal of the job
|
|
882
|
+
self.__logger.info(message)
|
|
883
|
+
|
|
884
|
+
# If the application is in debug mode, display the message on the console
|
|
885
|
+
if self.__app.config('app.debug', False):
|
|
886
|
+
self.__console.info(message)
|
|
887
|
+
|
|
888
|
+
# If a listener is registered for this job ID, invoke the listener with the event details
|
|
889
|
+
self.__taskCallableListener(event, ListeningEvent.JOB_ON_REMOVED)
|
|
716
890
|
|
|
717
891
|
def __loadEvents(
|
|
718
892
|
self
|
|
@@ -740,7 +914,7 @@ class Scheduler(ISchedule):
|
|
|
740
914
|
entity = event.toEntity()
|
|
741
915
|
|
|
742
916
|
# Add the job to the internal jobs list
|
|
743
|
-
self.__jobs.append(entity
|
|
917
|
+
self.__jobs.append(entity)
|
|
744
918
|
|
|
745
919
|
# Create a unique key for the job based on its signature
|
|
746
920
|
self.__scheduler.add_job(
|
|
@@ -754,82 +928,32 @@ class Scheduler(ISchedule):
|
|
|
754
928
|
replace_existing=True
|
|
755
929
|
)
|
|
756
930
|
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
args: Optional[List[str]] = None
|
|
761
|
-
) -> 'Event':
|
|
762
|
-
"""
|
|
763
|
-
Prepare an Event instance for a given command signature and its arguments.
|
|
764
|
-
|
|
765
|
-
This method validates the provided command signature and arguments, ensuring
|
|
766
|
-
that the command exists among the registered commands and that the arguments
|
|
767
|
-
are in the correct format. If validation passes, it creates and returns an
|
|
768
|
-
Event object representing the scheduled command, including its signature,
|
|
769
|
-
arguments, and description.
|
|
770
|
-
|
|
771
|
-
Parameters
|
|
772
|
-
----------
|
|
773
|
-
signature : str
|
|
774
|
-
The unique signature identifying the command to be scheduled. Must be a non-empty string.
|
|
775
|
-
args : Optional[List[str]], optional
|
|
776
|
-
A list of string arguments to be passed to the command. Defaults to None.
|
|
777
|
-
|
|
778
|
-
Returns
|
|
779
|
-
-------
|
|
780
|
-
Event
|
|
781
|
-
An Event instance containing the command signature, arguments, and its description.
|
|
782
|
-
|
|
783
|
-
Raises
|
|
784
|
-
------
|
|
785
|
-
ValueError
|
|
786
|
-
If the command signature is not a non-empty string, if the arguments are not a list
|
|
787
|
-
of strings or None, or if the command does not exist among the registered commands.
|
|
788
|
-
"""
|
|
789
|
-
|
|
790
|
-
# Validate that the command signature is a non-empty string
|
|
791
|
-
if not isinstance(signature, str) or not signature.strip():
|
|
792
|
-
raise ValueError("Command signature must be a non-empty string.")
|
|
793
|
-
|
|
794
|
-
# Ensure that arguments are either a list of strings or None
|
|
795
|
-
if args is not None and not isinstance(args, list):
|
|
796
|
-
raise ValueError("Arguments must be a list of strings or None.")
|
|
797
|
-
|
|
798
|
-
# Check if the command is available in the registered commands
|
|
799
|
-
if not self.__isAvailable(signature):
|
|
800
|
-
raise ValueError(f"The command '{signature}' is not available or does not exist.")
|
|
801
|
-
|
|
802
|
-
# Store the command and its arguments for scheduling
|
|
803
|
-
self.__events[signature] = Event(
|
|
804
|
-
signature=signature,
|
|
805
|
-
args=args or [],
|
|
806
|
-
purpose=self.__getDescription(signature)
|
|
807
|
-
)
|
|
808
|
-
|
|
809
|
-
# Return the Event instance for further scheduling configuration
|
|
810
|
-
return self.__events[signature]
|
|
931
|
+
# If a listener is associated with the event, register it
|
|
932
|
+
if entity.listener:
|
|
933
|
+
self._setListener(signature, entity.listener)
|
|
811
934
|
|
|
812
|
-
def
|
|
935
|
+
def setListener(
|
|
813
936
|
self,
|
|
814
|
-
event: str,
|
|
815
|
-
listener: callable
|
|
937
|
+
event: Union[str, ListeningEvent],
|
|
938
|
+
listener: Union[IScheduleEventListener, callable]
|
|
816
939
|
) -> None:
|
|
817
940
|
"""
|
|
818
941
|
Register a listener callback for a specific scheduler event.
|
|
819
942
|
|
|
820
|
-
This method
|
|
821
|
-
invoked when the specified scheduler event occurs. The event can be
|
|
822
|
-
|
|
823
|
-
|
|
943
|
+
This method registers a listener function or an instance of IScheduleEventListener
|
|
944
|
+
to be invoked when the specified scheduler event occurs. The event can be a global
|
|
945
|
+
event name (e.g., 'scheduler_started') or a specific job ID. The listener must be
|
|
946
|
+
callable and should accept the event object as a parameter.
|
|
824
947
|
|
|
825
948
|
Parameters
|
|
826
949
|
----------
|
|
827
950
|
event : str
|
|
828
951
|
The name of the event to listen for. This can be a global event name (e.g., 'scheduler_started')
|
|
829
952
|
or a specific job ID.
|
|
830
|
-
listener : callable
|
|
831
|
-
A callable function
|
|
832
|
-
The
|
|
953
|
+
listener : IScheduleEventListener or callable
|
|
954
|
+
A callable function or an instance of IScheduleEventListener that will be invoked
|
|
955
|
+
when the specified event occurs. The listener should accept one parameter, which
|
|
956
|
+
will be the event object.
|
|
833
957
|
|
|
834
958
|
Returns
|
|
835
959
|
-------
|
|
@@ -839,18 +963,23 @@ class Scheduler(ISchedule):
|
|
|
839
963
|
Raises
|
|
840
964
|
------
|
|
841
965
|
ValueError
|
|
842
|
-
If the event name is not a non-empty string or if the listener is not callable
|
|
966
|
+
If the event name is not a non-empty string or if the listener is not callable
|
|
967
|
+
or an instance of IScheduleEventListener.
|
|
843
968
|
"""
|
|
844
969
|
|
|
970
|
+
# If the event is an instance of ListeningEvent, extract its value
|
|
971
|
+
if isinstance(event, ListeningEvent):
|
|
972
|
+
event = event.value
|
|
973
|
+
|
|
845
974
|
# Validate that the event name is a non-empty string
|
|
846
975
|
if not isinstance(event, str) or not event.strip():
|
|
847
976
|
raise ValueError("Event name must be a non-empty string.")
|
|
848
977
|
|
|
849
|
-
# Validate that the listener is
|
|
850
|
-
if not callable(listener):
|
|
851
|
-
raise ValueError("Listener must be a callable function.")
|
|
978
|
+
# Validate that the listener is either callable or an instance of IScheduleEventListener
|
|
979
|
+
if not callable(listener) and not isinstance(listener, IScheduleEventListener):
|
|
980
|
+
raise ValueError("Listener must be a callable function or an instance of IScheduleEventListener.")
|
|
852
981
|
|
|
853
|
-
# Register the listener for the specified event
|
|
982
|
+
# Register the listener for the specified event in the internal listeners dictionary
|
|
854
983
|
self.__listeners[event] = listener
|
|
855
984
|
|
|
856
985
|
def pauseEverythingAt(
|
|
@@ -974,7 +1103,7 @@ class Scheduler(ISchedule):
|
|
|
974
1103
|
|
|
975
1104
|
# Add a job to the scheduler to shut it down at the specified datetime
|
|
976
1105
|
self.__scheduler.add_job(
|
|
977
|
-
func=self.shutdown, # Function to shut down the scheduler
|
|
1106
|
+
func=self.__scheduler.shutdown, # Function to shut down the scheduler
|
|
978
1107
|
trigger='date', # Trigger type is 'date' for one-time execution
|
|
979
1108
|
run_date=at, # The datetime at which the job will run
|
|
980
1109
|
id=f"shutdown_scheduler_at_{at.isoformat()}", # Unique job ID based on the datetime
|
|
@@ -1016,13 +1145,14 @@ class Scheduler(ISchedule):
|
|
|
1016
1145
|
try:
|
|
1017
1146
|
|
|
1018
1147
|
# Run indefinitely until interrupted
|
|
1019
|
-
while self.__scheduler.running
|
|
1148
|
+
while self.__scheduler.running:
|
|
1020
1149
|
await asyncio.sleep(1)
|
|
1021
1150
|
|
|
1022
1151
|
except (KeyboardInterrupt, asyncio.CancelledError):
|
|
1023
|
-
|
|
1024
|
-
# Handle graceful shutdown on keyboard interrupt or cancellation
|
|
1025
1152
|
await self.shutdown()
|
|
1153
|
+
except Exception as e:
|
|
1154
|
+
raise CLIOrionisRuntimeError(f"Failed to start the scheduler: {str(e)}") from e
|
|
1155
|
+
|
|
1026
1156
|
|
|
1027
1157
|
except Exception as e:
|
|
1028
1158
|
|
|
@@ -1033,92 +1163,186 @@ class Scheduler(ISchedule):
|
|
|
1033
1163
|
"""
|
|
1034
1164
|
Shut down the AsyncIO scheduler instance asynchronously.
|
|
1035
1165
|
|
|
1036
|
-
This method gracefully stops the AsyncIOScheduler that
|
|
1037
|
-
|
|
1166
|
+
This method gracefully stops the AsyncIOScheduler that manages asynchronous job execution.
|
|
1167
|
+
It ensures proper cleanup in asyncio environments and allows for an optional wait period
|
|
1168
|
+
to complete currently executing jobs before shutting down.
|
|
1038
1169
|
|
|
1039
1170
|
Parameters
|
|
1040
1171
|
----------
|
|
1041
1172
|
wait : bool, optional
|
|
1042
|
-
If True, the method
|
|
1043
|
-
If False, the scheduler
|
|
1173
|
+
If True, the method waits until all currently executing jobs are completed before shutting down the scheduler.
|
|
1174
|
+
If False, the scheduler shuts down immediately without waiting for running jobs to finish. Default is True.
|
|
1044
1175
|
|
|
1045
1176
|
Returns
|
|
1046
1177
|
-------
|
|
1047
1178
|
None
|
|
1048
|
-
This method does not return any value. It
|
|
1179
|
+
This method does not return any value. It performs the shutdown operation for the AsyncIO scheduler.
|
|
1180
|
+
|
|
1181
|
+
Raises
|
|
1182
|
+
------
|
|
1183
|
+
ValueError
|
|
1184
|
+
If the 'wait' parameter is not a boolean value.
|
|
1185
|
+
CLIOrionisRuntimeError
|
|
1186
|
+
If an error occurs during the shutdown process.
|
|
1049
1187
|
"""
|
|
1050
1188
|
|
|
1051
|
-
#
|
|
1189
|
+
# Ensure the 'wait' parameter is a boolean value.
|
|
1052
1190
|
if not isinstance(wait, bool):
|
|
1053
1191
|
raise ValueError("The 'wait' parameter must be a boolean value.")
|
|
1054
1192
|
|
|
1193
|
+
# If the scheduler is not running, there is nothing to shut down.
|
|
1194
|
+
if not self.__scheduler.running:
|
|
1195
|
+
return
|
|
1196
|
+
|
|
1197
|
+
try:
|
|
1198
|
+
# Shut down the AsyncIOScheduler. If 'wait' is True, it waits for currently executing jobs to finish.
|
|
1199
|
+
self.__scheduler.shutdown(wait=wait)
|
|
1200
|
+
|
|
1201
|
+
# If 'wait' is True, allow a small delay to ensure proper cleanup of resources.
|
|
1202
|
+
if wait:
|
|
1203
|
+
await asyncio.sleep(0)
|
|
1204
|
+
|
|
1205
|
+
except Exception as e:
|
|
1206
|
+
|
|
1207
|
+
# Raise a runtime error if the shutdown process fails.
|
|
1208
|
+
raise CLIOrionisRuntimeError(f"Failed to shut down the scheduler: {str(e)}") from e
|
|
1209
|
+
|
|
1210
|
+
def pause(self, signature: str) -> bool:
|
|
1211
|
+
"""
|
|
1212
|
+
Pause a scheduled job in the AsyncIO scheduler.
|
|
1213
|
+
|
|
1214
|
+
This method pauses a job in the AsyncIOScheduler identified by its unique signature.
|
|
1215
|
+
It validates the provided signature to ensure it is a non-empty string and attempts
|
|
1216
|
+
to pause the job. If the operation is successful, it logs the action and returns True.
|
|
1217
|
+
If the job cannot be paused (e.g., it does not exist), the method returns False.
|
|
1218
|
+
|
|
1219
|
+
Parameters
|
|
1220
|
+
----------
|
|
1221
|
+
signature : str
|
|
1222
|
+
The unique signature (ID) of the job to pause. This must be a non-empty string.
|
|
1223
|
+
|
|
1224
|
+
Returns
|
|
1225
|
+
-------
|
|
1226
|
+
bool
|
|
1227
|
+
True if the job was successfully paused.
|
|
1228
|
+
False if the job does not exist or an error occurred.
|
|
1229
|
+
|
|
1230
|
+
Raises
|
|
1231
|
+
------
|
|
1232
|
+
CLIOrionisValueError
|
|
1233
|
+
If the `signature` parameter is not a non-empty string.
|
|
1234
|
+
"""
|
|
1235
|
+
|
|
1236
|
+
# Validate that the signature is a non-empty string
|
|
1237
|
+
if not isinstance(signature, str) or not signature.strip():
|
|
1238
|
+
raise CLIOrionisValueError("Signature must be a non-empty string.")
|
|
1239
|
+
|
|
1055
1240
|
try:
|
|
1056
1241
|
|
|
1057
|
-
#
|
|
1058
|
-
|
|
1242
|
+
# Attempt to pause the job with the given signature
|
|
1243
|
+
self.__scheduler.pause_job(signature)
|
|
1244
|
+
|
|
1245
|
+
# Log the successful pausing of the job
|
|
1246
|
+
self.__logger.info(f"Job '{signature}' has been paused.")
|
|
1247
|
+
return True
|
|
1248
|
+
|
|
1249
|
+
except Exception:
|
|
1250
|
+
|
|
1251
|
+
# Return False if the job could not be paused (e.g., it does not exist)
|
|
1252
|
+
return False
|
|
1253
|
+
|
|
1254
|
+
def resume(self, signature: str) -> bool:
|
|
1255
|
+
"""
|
|
1256
|
+
Resume a paused job in the AsyncIO scheduler.
|
|
1257
|
+
|
|
1258
|
+
This method attempts to resume a job that was previously paused in the AsyncIOScheduler.
|
|
1259
|
+
It validates the provided job signature, ensures it is a non-empty string, and then
|
|
1260
|
+
resumes the job if it exists and is currently paused. If the operation is successful,
|
|
1261
|
+
it logs the action and returns True. If the job cannot be resumed (e.g., it does not exist),
|
|
1262
|
+
the method returns False.
|
|
1263
|
+
|
|
1264
|
+
Parameters
|
|
1265
|
+
----------
|
|
1266
|
+
signature : str
|
|
1267
|
+
The unique signature (ID) of the job to resume. This must be a non-empty string.
|
|
1268
|
+
|
|
1269
|
+
Returns
|
|
1270
|
+
-------
|
|
1271
|
+
bool
|
|
1272
|
+
True if the job was successfully resumed, False if the job does not exist or an error occurred.
|
|
1273
|
+
|
|
1274
|
+
Raises
|
|
1275
|
+
------
|
|
1276
|
+
CLIOrionisValueError
|
|
1277
|
+
If the `signature` parameter is not a non-empty string.
|
|
1278
|
+
"""
|
|
1279
|
+
|
|
1280
|
+
# Validate that the signature is a non-empty string
|
|
1281
|
+
if not isinstance(signature, str) or not signature.strip():
|
|
1282
|
+
raise CLIOrionisValueError("Signature must be a non-empty string.")
|
|
1059
1283
|
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1284
|
+
try:
|
|
1285
|
+
# Attempt to resume the job with the given signature
|
|
1286
|
+
self.__scheduler.resume_job(signature)
|
|
1063
1287
|
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1288
|
+
# Log the successful resumption of the job
|
|
1289
|
+
self.__logger.info(f"Job '{signature}' has been resumed.")
|
|
1290
|
+
return True
|
|
1067
1291
|
|
|
1068
1292
|
except Exception:
|
|
1069
1293
|
|
|
1070
|
-
#
|
|
1071
|
-
|
|
1294
|
+
# Return False if the job could not be resumed (e.g., it does not exist)
|
|
1295
|
+
return False
|
|
1072
1296
|
|
|
1073
|
-
|
|
1297
|
+
def remove(self, signature: str) -> bool:
|
|
1074
1298
|
"""
|
|
1075
|
-
Remove a scheduled job from the AsyncIO scheduler
|
|
1299
|
+
Remove a scheduled job from the AsyncIO scheduler.
|
|
1076
1300
|
|
|
1077
|
-
This method removes a job
|
|
1078
|
-
|
|
1079
|
-
|
|
1301
|
+
This method removes a job from the AsyncIOScheduler using its unique signature (ID).
|
|
1302
|
+
It validates the provided signature to ensure it is a non-empty string, attempts to
|
|
1303
|
+
remove the job from the scheduler, and updates the internal jobs list accordingly.
|
|
1304
|
+
If the operation is successful, it logs the action and returns True. If the job
|
|
1305
|
+
cannot be removed (e.g., it does not exist), the method returns False.
|
|
1080
1306
|
|
|
1081
1307
|
Parameters
|
|
1082
1308
|
----------
|
|
1083
1309
|
signature : str
|
|
1084
|
-
The signature of the
|
|
1310
|
+
The unique signature (ID) of the job to remove. This must be a non-empty string.
|
|
1085
1311
|
|
|
1086
1312
|
Returns
|
|
1087
1313
|
-------
|
|
1088
1314
|
bool
|
|
1089
|
-
|
|
1315
|
+
True if the job was successfully removed from the scheduler.
|
|
1316
|
+
False if the job does not exist or an error occurred.
|
|
1090
1317
|
|
|
1091
1318
|
Raises
|
|
1092
1319
|
------
|
|
1093
|
-
|
|
1094
|
-
If the signature is not a non-empty string.
|
|
1320
|
+
CLIOrionisValueError
|
|
1321
|
+
If the `signature` parameter is not a non-empty string.
|
|
1095
1322
|
"""
|
|
1096
1323
|
|
|
1097
1324
|
# Validate that the signature is a non-empty string
|
|
1098
1325
|
if not isinstance(signature, str) or not signature.strip():
|
|
1099
|
-
raise
|
|
1326
|
+
raise CLIOrionisValueError("Signature must be a non-empty string.")
|
|
1100
1327
|
|
|
1101
1328
|
try:
|
|
1102
1329
|
|
|
1103
|
-
#
|
|
1330
|
+
# Attempt to remove the job from the scheduler using its signature
|
|
1104
1331
|
self.__scheduler.remove_job(signature)
|
|
1105
1332
|
|
|
1106
|
-
#
|
|
1107
|
-
|
|
1108
|
-
|
|
1333
|
+
# Iterate through the internal jobs list to find and remove the job
|
|
1334
|
+
for job in self.__jobs:
|
|
1335
|
+
if job['signature'] == signature:
|
|
1336
|
+
self.__jobs.remove(job) # Remove the job from the internal list
|
|
1337
|
+
break
|
|
1109
1338
|
|
|
1110
|
-
#
|
|
1111
|
-
await asyncio.sleep(0.01)
|
|
1112
|
-
|
|
1113
|
-
# Log the removal of the job
|
|
1339
|
+
# Log the successful removal of the job
|
|
1114
1340
|
self.__logger.info(f"Job '{signature}' has been removed from the scheduler.")
|
|
1115
|
-
|
|
1116
|
-
# Return True to indicate successful removal
|
|
1117
1341
|
return True
|
|
1118
1342
|
|
|
1119
1343
|
except Exception:
|
|
1120
1344
|
|
|
1121
|
-
#
|
|
1345
|
+
# Return False if the job could not be removed (e.g., it does not exist)
|
|
1122
1346
|
return False
|
|
1123
1347
|
|
|
1124
1348
|
def events(self) -> list:
|