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