orionis 0.510.0__py3-none-any.whl → 0.512.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.
Files changed (39) hide show
  1. orionis/console/base/scheduler_event_listener.py +136 -0
  2. orionis/console/commands/scheduler_work.py +37 -25
  3. orionis/console/contracts/event.py +3032 -9
  4. orionis/console/contracts/schedule.py +34 -0
  5. orionis/console/contracts/schedule_event_listener.py +324 -0
  6. orionis/console/entities/all_jobs_removed.py +23 -0
  7. orionis/console/entities/executor_added.py +23 -0
  8. orionis/console/entities/executor_removed.py +25 -0
  9. orionis/console/entities/job_added.py +24 -0
  10. orionis/console/entities/job_error.py +35 -0
  11. orionis/console/entities/job_event_data.py +40 -0
  12. orionis/console/entities/job_executed.py +31 -0
  13. orionis/console/entities/job_max_instances.py +27 -0
  14. orionis/console/entities/job_missed.py +25 -0
  15. orionis/console/entities/job_modified.py +23 -0
  16. orionis/console/entities/job_pause.py +22 -0
  17. orionis/console/entities/job_removed.py +22 -0
  18. orionis/console/entities/job_resume.py +25 -0
  19. orionis/console/entities/job_store_added.py +24 -0
  20. orionis/console/entities/job_store_removed.py +25 -0
  21. orionis/console/entities/job_submitted.py +24 -0
  22. orionis/console/entities/scheduler_event_data.py +33 -0
  23. orionis/console/entities/scheduler_paused.py +17 -0
  24. orionis/console/entities/scheduler_resumed.py +24 -0
  25. orionis/console/entities/scheduler_shutdown.py +23 -0
  26. orionis/console/entities/scheduler_started.py +21 -0
  27. orionis/console/enums/listener.py +75 -0
  28. orionis/console/tasks/event.py +3703 -21
  29. orionis/console/tasks/schedule.py +702 -48
  30. orionis/metadata/framework.py +1 -1
  31. {orionis-0.510.0.dist-info → orionis-0.512.0.dist-info}/METADATA +1 -1
  32. {orionis-0.510.0.dist-info → orionis-0.512.0.dist-info}/RECORD +36 -15
  33. orionis/console/contracts/listener.py +0 -132
  34. orionis/console/entities/listeners.py +0 -241
  35. orionis/console/tasks/exception_report.py +0 -94
  36. {orionis-0.510.0.dist-info → orionis-0.512.0.dist-info}/WHEEL +0 -0
  37. {orionis-0.510.0.dist-info → orionis-0.512.0.dist-info}/licenses/LICENCE +0 -0
  38. {orionis-0.510.0.dist-info → orionis-0.512.0.dist-info}/top_level.txt +0 -0
  39. {orionis-0.510.0.dist-info → orionis-0.512.0.dist-info}/zip-safe +0 -0
@@ -1,15 +1,38 @@
1
1
  import asyncio
2
+ from datetime import datetime
2
3
  import logging
3
4
  from typing import Dict, List, Optional
4
5
  import pytz
5
- from apscheduler.events import EVENT_JOB_MISSED, EVENT_JOB_ERROR, EVENT_ALL, EVENT_SCHEDULER_STARTED
6
+ from apscheduler.events import (
7
+ EVENT_JOB_ERROR,
8
+ EVENT_JOB_EXECUTED,
9
+ EVENT_JOB_MISSED,
10
+ EVENT_JOB_SUBMITTED,
11
+ EVENT_SCHEDULER_PAUSED,
12
+ EVENT_SCHEDULER_RESUMED,
13
+ EVENT_SCHEDULER_SHUTDOWN,
14
+ EVENT_SCHEDULER_STARTED,
15
+ EVENT_JOB_MAX_INSTANCES
16
+ )
6
17
  from apscheduler.schedulers.asyncio import AsyncIOScheduler as APSAsyncIOScheduler
18
+ from rich.console import Console
19
+ from rich.panel import Panel
20
+ from rich.text import Text
7
21
  from orionis.console.contracts.reactor import IReactor
8
22
  from orionis.console.contracts.schedule import ISchedule
23
+ from orionis.console.entities.job_error import JobError
24
+ from orionis.console.entities.job_executed import JobExecuted
25
+ from orionis.console.entities.job_max_instances import JobMaxInstances
26
+ from orionis.console.entities.job_missed import JobMissed
27
+ from orionis.console.entities.job_submitted import JobSubmitted
28
+ from orionis.console.entities.scheduler_paused import SchedulerPaused
29
+ from orionis.console.entities.scheduler_resumed import SchedulerResumed
30
+ from orionis.console.entities.scheduler_shutdown import SchedulerShutdown
31
+ from orionis.console.entities.scheduler_started import SchedulerStarted
32
+ from orionis.console.enums.listener import ListeningEvent
9
33
  from orionis.console.exceptions import CLIOrionisRuntimeError
10
34
  from orionis.console.output.contracts.console import IConsole
11
35
  from orionis.console.tasks.event import Event
12
- from orionis.console.tasks.exception_report import ScheduleErrorReporter
13
36
  from orionis.foundation.contracts.application import IApplication
14
37
  from orionis.services.log.contracts.log_service import ILogger
15
38
 
@@ -20,7 +43,7 @@ class Scheduler(ISchedule):
20
43
  reactor: IReactor,
21
44
  app: IApplication,
22
45
  console: IConsole,
23
- error_reporter: ScheduleErrorReporter
46
+ rich_console: Console
24
47
  ) -> None:
25
48
  """
26
49
  Initialize a new instance of the Scheduler class.
@@ -48,8 +71,8 @@ class Scheduler(ISchedule):
48
71
  # Store the console instance for output operations.
49
72
  self.__console = console
50
73
 
51
- # Store the error reporter instance for handling exceptions.
52
- self.__error_reporter = error_reporter
74
+ # Store the rich console instance for advanced output formatting.
75
+ self.__rich_console = rich_console
53
76
 
54
77
  # Initialize AsyncIOScheduler instance with timezone configuration.
55
78
  self.__scheduler: APSAsyncIOScheduler = APSAsyncIOScheduler(
@@ -79,50 +102,525 @@ class Scheduler(ISchedule):
79
102
  # Initialize the jobs list to keep track of all scheduled jobs.
80
103
  self.__jobs: List[dict] = []
81
104
 
82
- # Add a listener to the scheduler to capture job events such as missed jobs or errors.
83
- if self.__app.config('app.debug', False):
84
- self.__scheduler.add_listener(self.__listener, EVENT_ALL)
105
+ # Initialize the listeners dictionary to manage event listeners.
106
+ self.__listeners: Dict[str, callable] = {}
107
+
108
+ def __getCurrentTime(
109
+ self
110
+ ) -> str:
111
+ """
112
+ Get the current date and time formatted as a string.
113
+
114
+ This method retrieves the current date and time in the timezone configured
115
+ for the application and formats it as a string in the "YYYY-MM-DD HH:MM:SS" format.
116
+
117
+ Returns
118
+ -------
119
+ str
120
+ A string representing the current date and time in the configured timezone,
121
+ formatted as "YYYY-MM-DD HH:MM:SS".
122
+ """
85
123
 
86
- def __listener(self, event):
124
+ # Get the current time in the configured timezone
125
+ now = pytz.timezone(self.__app.config('app.timezone', 'UTC')).localize(
126
+ pytz.datetime.datetime.now()
127
+ )
128
+
129
+ # Return the formatted date and time string
130
+ return now.strftime('%Y-%m-%d %H:%M:%S')
131
+
132
+ def __suscribeListeners(
133
+ self
134
+ ) -> None:
87
135
  """
88
- Handle job events by logging errors and missed jobs.
136
+ Subscribe to scheduler events for monitoring and handling.
89
137
 
90
- This method acts as a listener for job events emitted by the scheduler. It processes
91
- two main types of events: job execution errors and missed job executions. When a job
92
- raises an exception during execution, the method logs the error and delegates reporting
93
- to the error reporter. If a job is missed (i.e., not executed at its scheduled time),
94
- the method logs a warning and notifies the error reporter accordingly.
138
+ This method sets up event listeners for the AsyncIOScheduler instance to monitor
139
+ various scheduler events such as scheduler start, shutdown, pause, resume, job submission,
140
+ execution, missed jobs, and errors. Each listener is associated with a specific event type
141
+ and is responsible for handling the corresponding event.
142
+
143
+ The listeners log relevant information, invoke registered callbacks, and handle errors
144
+ or missed jobs as needed. This ensures that the scheduler's state and job execution
145
+ are monitored effectively.
146
+
147
+ Returns
148
+ -------
149
+ None
150
+ This method does not return any value. It configures event listeners on the scheduler.
151
+ """
152
+
153
+ # Add a listener for the scheduler started event
154
+ self.__scheduler.add_listener(self.__startedListener, EVENT_SCHEDULER_STARTED)
155
+
156
+ # Add a listener for the scheduler shutdown event
157
+ self.__scheduler.add_listener(self.__shutdownListener, EVENT_SCHEDULER_SHUTDOWN)
158
+
159
+ # Add a listener for the scheduler paused event
160
+ self.__scheduler.add_listener(self.__pausedListener, EVENT_SCHEDULER_PAUSED)
161
+
162
+ # Add a listener for the scheduler resumed event
163
+ self.__scheduler.add_listener(self.__resumedListener, EVENT_SCHEDULER_RESUMED)
164
+
165
+ # Add a listener for job submission events
166
+ self.__scheduler.add_listener(self.__submittedListener, EVENT_JOB_SUBMITTED)
167
+
168
+ # Add a listener for job execution events
169
+ self.__scheduler.add_listener(self.__executedListener, EVENT_JOB_EXECUTED)
170
+
171
+ # Add a listener for missed job events
172
+ self.__scheduler.add_listener(self.__missedListener, EVENT_JOB_MISSED)
173
+
174
+ # Add a listener for job error events
175
+ self.__scheduler.add_listener(self.__errorListener, EVENT_JOB_ERROR)
176
+
177
+ # Add a listener for job max instances events
178
+ self.__scheduler.add_listener(self.__maxInstancesListener, EVENT_JOB_MAX_INSTANCES)
179
+
180
+ def __startedListener(
181
+ self,
182
+ event: SchedulerStarted
183
+ ) -> None:
184
+ """
185
+ Handle the scheduler started event for logging and invoking registered listeners.
186
+
187
+ This method is triggered when the scheduler starts. It logs an informational
188
+ message indicating that the scheduler has started successfully and displays
189
+ a formatted message on the rich console. If a listener is registered for the
190
+ scheduler started event, it invokes the listener with the event details.
95
191
 
96
192
  Parameters
97
193
  ----------
98
- event : apscheduler.events.JobEvent
99
- The job event object containing information about the job execution, such as
100
- job_id, exception, traceback, code, and scheduled_run_time.
194
+ event : SchedulerStarted
195
+ An event object containing details about the scheduler start event.
101
196
 
102
197
  Returns
103
198
  -------
104
199
  None
105
- This method does not return any value. It performs logging and error reporting
106
- based on the event type.
200
+ This method does not return any value. It performs logging, displays
201
+ a message on the console, and invokes any registered listener for the
202
+ scheduler started event.
107
203
  """
108
204
 
109
- # If the event contains an exception, log the error and report it
110
- if event.exception:
111
- self.__logger.error(f"Job {event.job_id} raised an exception: {event.exception}")
112
- self.__error_reporter.reportException(
113
- job_id=event.job_id,
114
- exception=event.exception,
115
- traceback=event.traceback
205
+ # Get the current time in the configured timezone
206
+ now = self.__getCurrentTime()
207
+
208
+ # Log an informational message indicating that the scheduler has started
209
+ self.__logger.info(f"Orionis Scheduler started successfully at {now}.")
210
+
211
+ # Display a start message for the scheduler worker on the rich console
212
+ # Add a blank line for better formatting
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
116
223
  )
117
224
 
118
- # If the event indicates a missed job, log a warning and report it
119
- elif event.code == EVENT_JOB_MISSED:
120
- self.__logger.warning(f"Job {event.job_id} was missed, scheduled for {event.scheduled_run_time}")
121
- self.__error_reporter.reportMissed(
122
- job_id=event.job_id,
123
- scheduled_time=event.scheduled_run_time
225
+ # Display the message in a styled panel
226
+ self.__rich_console.print(
227
+ Panel(panel_content, border_style="green", padding=(1, 2))
124
228
  )
125
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
235
+
236
+ # Check if a listener is registered for the scheduler started event
237
+ if scheduler_started in self.__listeners:
238
+ listener = self.__listeners[scheduler_started]
239
+
240
+ # Ensure the listener is callable before invoking it
241
+ if callable(listener):
242
+
243
+ # Invoke the registered listener with the event details
244
+ listener(event)
245
+
246
+ def __shutdownListener(
247
+ self,
248
+ event: SchedulerShutdown
249
+ ) -> None:
250
+ """
251
+ Handle the scheduler shutdown event for logging and invoking registered listeners.
252
+
253
+ This method is triggered when the scheduler shuts down. It logs an informational
254
+ message indicating that the scheduler has shut down successfully and displays
255
+ a formatted message on the rich console. If a listener is registered for the
256
+ scheduler shutdown event, it invokes the listener with the event details.
257
+
258
+ Parameters
259
+ ----------
260
+ event : SchedulerShutdown
261
+ An event object containing details about the scheduler shutdown event.
262
+
263
+ Returns
264
+ -------
265
+ None
266
+ This method does not return any value. It performs logging, displays
267
+ a message on the console, and invokes any registered listener for the
268
+ scheduler shutdown event.
269
+ """
270
+
271
+ # Get the current time in the configured timezone
272
+ now = self.__getCurrentTime()
273
+
274
+ # Create a shutdown message
275
+ message = f"Orionis Scheduler shut down successfully at {now}."
276
+
277
+ # Log an informational message indicating that the scheduler has shut down
278
+ self.__logger.info(message)
279
+
280
+ # Display a shutdown message for the scheduler worker on console
281
+ if self.__app.config('app.debug', False):
282
+ self.__console.info(message)
283
+
284
+ # Retrieve the global identifier for the scheduler shutdown event
285
+ scheduler_shutdown = GlobalListener.SCHEDULER_SHUTDOWN.value
286
+
287
+ # Check if a listener is registered for the scheduler shutdown event
288
+ if scheduler_shutdown in self.__listeners:
289
+ listener = self.__listeners[scheduler_shutdown]
290
+
291
+ # Ensure the listener is callable before invoking it
292
+ if callable(listener):
293
+ # Invoke the registered listener with the event details
294
+ listener(event)
295
+
296
+ def __pausedListener(
297
+ self,
298
+ event: SchedulerPaused
299
+ ) -> None:
300
+ """
301
+ Handle the scheduler paused event for logging and invoking registered listeners.
302
+
303
+ This method is triggered when the scheduler is paused. It logs an informational
304
+ message indicating that the scheduler has been paused successfully and displays
305
+ a formatted message on the rich console. If a listener is registered for the
306
+ scheduler paused event, it invokes the listener with the event details.
307
+
308
+ Parameters
309
+ ----------
310
+ event : SchedulerPaused
311
+ An event object containing details about the scheduler paused event.
312
+
313
+ Returns
314
+ -------
315
+ None
316
+ This method does not return any value. It performs logging, displays
317
+ a message on the console, and invokes any registered listener for the
318
+ scheduler paused event.
319
+ """
320
+
321
+ # Get the current time in the configured timezone
322
+ now = self.__getCurrentTime()
323
+
324
+ # Create a paused message
325
+ message = f"Orionis Scheduler paused successfully at {now}."
326
+
327
+ # Log an informational message indicating that the scheduler has been paused
328
+ self.__logger.info(message)
329
+
330
+ # Display a paused message for the scheduler worker on console
331
+ if self.__app.config('app.debug', False):
332
+ self.__console.info(message)
333
+
334
+ # Retrieve the global identifier for the scheduler paused event
335
+ scheduler_paused = GlobalListener.SCHEDULER_PAUSED.value
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)
345
+
346
+ def __resumedListener(
347
+ self,
348
+ event: SchedulerResumed
349
+ ) -> None:
350
+ """
351
+ Handle the scheduler resumed event for logging and invoking registered listeners.
352
+
353
+ This method is triggered when the scheduler resumes from a paused state. It logs an informational
354
+ message indicating that the scheduler has resumed successfully and displays a formatted message
355
+ on the rich console. If a listener is registered for the scheduler resumed event, it invokes
356
+ the listener with the event details.
357
+
358
+ Parameters
359
+ ----------
360
+ event : SchedulerResumed
361
+ An event object containing details about the scheduler resumed event.
362
+
363
+ Returns
364
+ -------
365
+ None
366
+ This method does not return any value. It performs logging, displays
367
+ a message on the console, and invokes any registered listener for the
368
+ scheduler resumed event.
369
+ """
370
+
371
+ # Get the current time in the configured timezone
372
+ now = self.__getCurrentTime()
373
+
374
+ # Create a resumed message
375
+ message = f"Orionis Scheduler resumed successfully at {now}."
376
+
377
+ # Log an informational message indicating that the scheduler has resumed
378
+ self.__logger.info(message)
379
+
380
+ # Display a resumed message for the scheduler worker on console
381
+ if self.__app.config('app.debug', False):
382
+ self.__console.info(message)
383
+
384
+ # Retrieve the global identifier for the scheduler resumed event
385
+ scheduler_resumed = GlobalListener.SCHEDULER_RESUMED.value
386
+
387
+ # Check if a listener is registered for the scheduler resumed event
388
+ if scheduler_resumed in self.__listeners:
389
+ listener = self.__listeners[scheduler_resumed]
390
+
391
+ # Ensure the listener is callable before invoking it
392
+ if callable(listener):
393
+ # Invoke the registered listener with the event details
394
+ listener(event)
395
+
396
+ def __submittedListener(
397
+ self,
398
+ event: JobSubmitted
399
+ ) -> None:
400
+ """
401
+ Handle job submission events for logging and error reporting.
402
+
403
+ This method is triggered when a job is submitted to its executor. It logs an informational
404
+ message indicating that the job has been submitted successfully. If the application is in
405
+ debug mode, it also displays a message on the console. Additionally, if a listener is
406
+ registered for the submitted job, it invokes the listener with the event details.
407
+
408
+ Parameters
409
+ ----------
410
+ event : JobSubmitted
411
+ An instance of the JobSubmitted containing details about the submitted job,
412
+ including its ID and scheduled run times.
413
+
414
+ Returns
415
+ -------
416
+ None
417
+ This method does not return any value. It performs logging, error reporting,
418
+ and listener invocation for the job submission event.
419
+ """
420
+
421
+ # Create a submission message
422
+ message = f"Task {event.job_id} submitted to executor."
423
+
424
+ # Log an informational message indicating that the job has been submitted
425
+ self.__logger.info(message)
426
+
427
+ # If the application is in debug mode, display a message on the console
428
+ if self.__app.config('app.debug', False):
429
+ self.__console.info(message)
430
+
431
+ # If a listener is registered for this job ID, invoke the listener with the event details
432
+ if event.job_id in self.__listeners:
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)
440
+
441
+ def __executedListener(
442
+ self,
443
+ event: JobExecuted
444
+ ) -> None:
445
+ """
446
+ Handle job execution events for logging and error reporting.
447
+
448
+ This method is triggered when a job is executed by its executor. It logs an informational
449
+ message indicating that the job has been executed successfully. If the application is in
450
+ debug mode, it also displays a message on the console. If the job execution resulted in
451
+ an exception, it logs the error and reports it using the error reporter. Additionally,
452
+ if a listener is registered for the executed job, it invokes the listener with the event details.
453
+
454
+ Parameters
455
+ ----------
456
+ event : JobExecuted
457
+ An instance of the JobExecuted containing details about the executed job,
458
+ including its ID, return value, exception (if any), and traceback.
459
+
460
+ Returns
461
+ -------
462
+ None
463
+ This method does not return any value. It performs logging, error reporting,
464
+ and listener invocation for the job execution event.
465
+ """
466
+
467
+ # Create an execution message
468
+ message = f"Task {event.job_id} executed."
469
+
470
+ # Log an informational message indicating that the job has been executed
471
+ self.__logger.info(message)
472
+
473
+ # If the application is in debug mode, display a message on the console
474
+ if self.__app.config('app.debug', False):
475
+ self.__console.info(message)
476
+
477
+ # If a listener is registered for this job ID, invoke the listener with the event details
478
+ if event.job_id in self.__listeners:
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)
486
+
487
+ def __missedListener(
488
+ self,
489
+ event: JobMissed
490
+ ) -> None:
491
+ """
492
+ Handle job missed events for debugging and error reporting.
493
+
494
+ This method is triggered when a scheduled job is missed. It logs a warning
495
+ message indicating the missed job and its scheduled run time. If the application
496
+ is in debug mode, it reports the missed job using the error reporter. Additionally,
497
+ if a listener is registered for the missed job, it invokes the listener with the
498
+ event details.
499
+
500
+ Parameters
501
+ ----------
502
+ event : JobMissed
503
+ An instance of the JobMissed event containing details about the missed job,
504
+ including its ID and scheduled run time.
505
+
506
+ Returns
507
+ -------
508
+ None
509
+ This method does not return any value. It performs logging, error reporting,
510
+ and listener invocation for the missed job event.
511
+ """
512
+
513
+ # Create a missed job message
514
+ message = f"Task {event.job_id} was missed. It was scheduled to run at {event.scheduled_run_time}."
515
+
516
+ # Log a warning indicating that the job was missed
517
+ self.__logger.warning(message)
518
+
519
+ # If the application is in debug mode, report the missed job using the error reporter
520
+ if self.__app.config('app.debug', False):
521
+ self.__console.warning(message)
522
+
523
+ # If a listener is registered for this job ID, invoke the listener with the event details
524
+ if event.job_id in self.__listeners:
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)
577
+
578
+ def __maxInstancesListener(
579
+ self,
580
+ event: JobMaxInstances
581
+ ) -> None:
582
+ """
583
+ Handle job max instances events for logging and error reporting.
584
+
585
+ This method is triggered when a job execution exceeds the maximum allowed
586
+ concurrent instances. It logs an error message indicating the job ID and
587
+ the exception raised. If the application is in debug mode, it also reports
588
+ the error using the error reporter. Additionally, if a listener is registered
589
+ for the job that exceeded max instances, it invokes the listener with the event details.
590
+
591
+ Parameters
592
+ ----------
593
+ event : JobMaxInstances
594
+ An instance of the JobMaxInstances event containing details about the job that
595
+ exceeded max instances, including its ID and the exception raised.
596
+
597
+ Returns
598
+ -------
599
+ None
600
+ This method does not return any value. It performs logging, error reporting,
601
+ and listener invocation for the job max instances event.
602
+ """
603
+
604
+ # Create a max instances error message
605
+ message = f"Task {event.job_id} exceeded maximum instances"
606
+
607
+ # Log an error message indicating that the job exceeded maximum instances
608
+ self.__logger.error(message)
609
+
610
+ # If the application is in debug mode, display a message on the console
611
+ if self.__app.config('app.debug', False):
612
+ self.__console.error(message)
613
+
614
+ # If a listener is registered for this job ID, invoke the listener with the event details
615
+ if event.job_id in self.__listeners:
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
+
126
624
  def __getCommands(
127
625
  self
128
626
  ) -> dict:
@@ -311,25 +809,178 @@ class Scheduler(ISchedule):
311
809
  # Return the Event instance for further scheduling configuration
312
810
  return self.__events[signature]
313
811
 
314
- def addListenerOnSchedulerStarted(
812
+ def _setListener(
315
813
  self,
814
+ event: str,
316
815
  listener: callable
317
816
  ) -> None:
318
817
  """
319
- Add a listener for the scheduler started event.
818
+ Register a listener callback for a specific scheduler event.
320
819
 
321
- This method allows you to register a callback function that will be called
322
- when the scheduler starts. The callback should accept a single argument, which
323
- is the event object containing details about the scheduler start event.
820
+ This method allows the registration of a callable listener function that will be
821
+ invoked when the specified scheduler event occurs. The event can be one of the
822
+ predefined global events or a specific job ID. The listener must be a callable
823
+ function that accepts a single argument, which will be the event object.
324
824
 
325
825
  Parameters
326
826
  ----------
827
+ event : str
828
+ The name of the event to listen for. This can be a global event name (e.g., 'scheduler_started')
829
+ or a specific job ID.
327
830
  listener : callable
328
- A function that will be called when the scheduler starts.
329
- It should accept one parameter, which is the event object.
831
+ A callable function that will be invoked when the specified event occurs.
832
+ The function should accept one parameter, which will be the event object.
833
+
834
+ Returns
835
+ -------
836
+ None
837
+ This method does not return any value. It registers the listener for the specified event.
838
+
839
+ Raises
840
+ ------
841
+ ValueError
842
+ If the event name is not a non-empty string or if the listener is not callable.
843
+ """
844
+
845
+ # Validate that the event name is a non-empty string
846
+ if not isinstance(event, str) or not event.strip():
847
+ raise ValueError("Event name must be a non-empty string.")
848
+
849
+ # Validate that the listener is a callable function
850
+ if not callable(listener):
851
+ raise ValueError("Listener must be a callable function.")
852
+
853
+ # Register the listener for the specified event
854
+ self.__listeners[event] = listener
855
+
856
+ def pauseEverythingAt(
857
+ self,
858
+ at: datetime
859
+ ) -> None:
330
860
  """
331
- # Register the listener for the scheduler started event
332
- self.__scheduler.add_listener(listener, EVENT_SCHEDULER_STARTED)
861
+ Schedule the scheduler to pause all operations at a specific datetime.
862
+
863
+ This method allows you to schedule a job that will pause the AsyncIOScheduler
864
+ at the specified datetime. The job is added to the scheduler with a 'date'
865
+ trigger, ensuring it executes exactly at the given time.
866
+
867
+ Parameters
868
+ ----------
869
+ at : datetime
870
+ The datetime at which the scheduler should be paused. Must be a valid
871
+ datetime object.
872
+
873
+ Returns
874
+ -------
875
+ None
876
+ This method does not return any value. It schedules a job to pause the
877
+ scheduler at the specified datetime.
878
+
879
+ Raises
880
+ ------
881
+ ValueError
882
+ If the 'at' parameter is not a valid datetime object.
883
+ """
884
+
885
+ # Validate that the 'at' parameter is a datetime object
886
+ if not isinstance(at, datetime):
887
+ raise ValueError("The 'at' parameter must be a datetime object.")
888
+
889
+ # Add a job to the scheduler to pause it at the specified datetime
890
+ self.__scheduler.add_job(
891
+ func=self.__scheduler.pause, # Function to pause the scheduler
892
+ trigger='date', # Trigger type is 'date' for one-time execution
893
+ run_date=at, # The datetime at which the job will run
894
+ id=f"pause_scheduler_at_{at.isoformat()}", # Unique job ID based on the datetime
895
+ name=f"Pause Scheduler at {at.isoformat()}", # Descriptive name for the job
896
+ replace_existing=True # Replace any existing job with the same ID
897
+ )
898
+
899
+ def resumeEverythingAt(
900
+ self,
901
+ at: datetime
902
+ ) -> None:
903
+ """
904
+ Schedule the scheduler to resume all operations at a specific datetime.
905
+
906
+ This method allows you to schedule a job that will resume the AsyncIOScheduler
907
+ at the specified datetime. The job is added to the scheduler with a 'date'
908
+ trigger, ensuring it executes exactly at the given time.
909
+
910
+ Parameters
911
+ ----------
912
+ at : datetime
913
+ The datetime at which the scheduler should be resumed. Must be a valid
914
+ datetime object.
915
+
916
+ Returns
917
+ -------
918
+ None
919
+ This method does not return any value. It schedules a job to resume the
920
+ scheduler at the specified datetime.
921
+
922
+ Raises
923
+ ------
924
+ ValueError
925
+ If the 'at' parameter is not a valid datetime object.
926
+ """
927
+
928
+ # Validate that the 'at' parameter is a datetime object
929
+ if not isinstance(at, datetime):
930
+ raise ValueError("The 'at' parameter must be a datetime object.")
931
+
932
+ # Add a job to the scheduler to resume it at the specified datetime
933
+ self.__scheduler.add_job(
934
+ func=self.__scheduler.resume, # Function to resume the scheduler
935
+ trigger='date', # Trigger type is 'date' for one-time execution
936
+ run_date=at, # The datetime at which the job will run
937
+ id=f"resume_scheduler_at_{at.isoformat()}", # Unique job ID based on the datetime
938
+ name=f"Resume Scheduler at {at.isoformat()}", # Descriptive name for the job
939
+ replace_existing=True # Replace any existing job with the same ID
940
+ )
941
+
942
+ def shutdownEverythingAt(
943
+ self,
944
+ at: datetime
945
+ ) -> None:
946
+ """
947
+ Schedule the scheduler to shut down all operations at a specific datetime.
948
+
949
+ This method allows you to schedule a job that will shut down the AsyncIOScheduler
950
+ at the specified datetime. The job is added to the scheduler with a 'date'
951
+ trigger, ensuring it executes exactly at the given time.
952
+
953
+ Parameters
954
+ ----------
955
+ at : datetime
956
+ The datetime at which the scheduler should be shut down. Must be a valid
957
+ datetime object.
958
+
959
+ Returns
960
+ -------
961
+ None
962
+ This method does not return any value. It schedules a job to shut down the
963
+ scheduler at the specified datetime.
964
+
965
+ Raises
966
+ ------
967
+ ValueError
968
+ If the 'at' parameter is not a valid datetime object.
969
+ """
970
+
971
+ # Validate that the 'at' parameter is a datetime object
972
+ if not isinstance(at, datetime):
973
+ raise ValueError("The 'at' parameter must be a datetime object.")
974
+
975
+ # Add a job to the scheduler to shut it down at the specified datetime
976
+ self.__scheduler.add_job(
977
+ func=self.shutdown, # Function to shut down the scheduler
978
+ trigger='date', # Trigger type is 'date' for one-time execution
979
+ run_date=at, # The datetime at which the job will run
980
+ id=f"shutdown_scheduler_at_{at.isoformat()}", # Unique job ID based on the datetime
981
+ name=f"Shutdown Scheduler at {at.isoformat()}", # Descriptive name for the job
982
+ replace_existing=True # Replace any existing job with the same ID
983
+ )
333
984
 
334
985
  async def start(self) -> None:
335
986
  """
@@ -348,22 +999,28 @@ class Scheduler(ISchedule):
348
999
  # Start the AsyncIOScheduler to handle asynchronous jobs.
349
1000
  try:
350
1001
 
351
- # Load existing events into the scheduler
352
- self.events()
1002
+ # Ensure all events are loaded into the internal jobs list
1003
+ self.__loadEvents()
1004
+
1005
+ # Subscribe to scheduler events for monitoring and handling
1006
+ self.__suscribeListeners()
353
1007
 
354
1008
  # Ensure we're in an asyncio context
355
1009
  asyncio.get_running_loop()
356
1010
 
357
1011
  # Start the scheduler
358
1012
  if not self.__scheduler.running:
359
- self.__logger.info(f"Orionis Scheduler started. {len(self.__jobs)} jobs scheduled.")
360
1013
  self.__scheduler.start()
361
1014
 
362
1015
  # Keep the event loop alive to process scheduled jobs
363
1016
  try:
1017
+
1018
+ # Run indefinitely until interrupted
364
1019
  while self.__scheduler.running and self.__scheduler.get_jobs():
365
1020
  await asyncio.sleep(1)
1021
+
366
1022
  except (KeyboardInterrupt, asyncio.CancelledError):
1023
+
367
1024
  # Handle graceful shutdown on keyboard interrupt or cancellation
368
1025
  await self.shutdown()
369
1026
 
@@ -408,9 +1065,6 @@ class Scheduler(ISchedule):
408
1065
  if wait:
409
1066
  await asyncio.sleep(0.1)
410
1067
 
411
- # Log the shutdown of the scheduler
412
- self.__logger.info("Orionis Scheduler has been shut down.")
413
-
414
1068
  except Exception:
415
1069
 
416
1070
  # AsyncIOScheduler may not be running or may have issues in shutdown