orionis 0.726.0__py3-none-any.whl → 0.727.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/commands/scheduler_work.py +1 -0
- orionis/console/contracts/event.py +1 -1
- orionis/console/contracts/schedule.py +3 -3
- orionis/console/entities/event.py +4 -1
- orionis/console/fluent/event.py +63 -17
- orionis/console/tasks/schedule.py +296 -147
- orionis/metadata/framework.py +1 -1
- {orionis-0.726.0.dist-info → orionis-0.727.0.dist-info}/METADATA +1 -1
- {orionis-0.726.0.dist-info → orionis-0.727.0.dist-info}/RECORD +12 -12
- {orionis-0.726.0.dist-info → orionis-0.727.0.dist-info}/WHEEL +0 -0
- {orionis-0.726.0.dist-info → orionis-0.727.0.dist-info}/licenses/LICENCE +0 -0
- {orionis-0.726.0.dist-info → orionis-0.727.0.dist-info}/top_level.txt +0 -0
|
@@ -99,6 +99,7 @@ class ScheduleWorkCommand(BaseCommand):
|
|
|
99
99
|
console.line()
|
|
100
100
|
console.print(Panel("No scheduled jobs found.", border_style="green"))
|
|
101
101
|
console.line()
|
|
102
|
+
return
|
|
102
103
|
|
|
103
104
|
# If there are scheduled jobs and the scheduler has an onStarted method
|
|
104
105
|
if rf_scheduler.hasMethod("onStarted"):
|
|
@@ -187,7 +187,7 @@ class ISchedule(ABC):
|
|
|
187
187
|
pass
|
|
188
188
|
|
|
189
189
|
@abstractmethod
|
|
190
|
-
def
|
|
190
|
+
def pauseCommand(self, signature: str) -> bool:
|
|
191
191
|
"""
|
|
192
192
|
Pause a scheduled job in the AsyncIO scheduler.
|
|
193
193
|
|
|
@@ -215,7 +215,7 @@ class ISchedule(ABC):
|
|
|
215
215
|
pass
|
|
216
216
|
|
|
217
217
|
@abstractmethod
|
|
218
|
-
def
|
|
218
|
+
def resumeCommand(self, signature: str) -> bool:
|
|
219
219
|
"""
|
|
220
220
|
Resume a paused job in the AsyncIO scheduler.
|
|
221
221
|
|
|
@@ -243,7 +243,7 @@ class ISchedule(ABC):
|
|
|
243
243
|
pass
|
|
244
244
|
|
|
245
245
|
@abstractmethod
|
|
246
|
-
def
|
|
246
|
+
def removeCommand(self, signature: str) -> bool:
|
|
247
247
|
"""
|
|
248
248
|
Remove a scheduled job from the AsyncIO scheduler.
|
|
249
249
|
|
orionis/console/fluent/event.py
CHANGED
|
@@ -83,7 +83,10 @@ class Event(IEvent):
|
|
|
83
83
|
self.__max_instances: Optional[int] = 1
|
|
84
84
|
|
|
85
85
|
# Initialize the misfire grace time attribute as None
|
|
86
|
-
self.__misfire_grace_time: Optional[int] =
|
|
86
|
+
self.__misfire_grace_time: Optional[int] = 1
|
|
87
|
+
|
|
88
|
+
# Initialize the coalesce attribute as True
|
|
89
|
+
self.__coalesce: bool = True
|
|
87
90
|
|
|
88
91
|
def toEntity( # NOSONAR
|
|
89
92
|
self
|
|
@@ -141,9 +144,13 @@ class Event(IEvent):
|
|
|
141
144
|
raise CLIOrionisValueError("Max instances must be a positive integer or None.")
|
|
142
145
|
|
|
143
146
|
# Validate that misfire_grace_time is a positive integer if it is set
|
|
144
|
-
if self.__misfire_grace_time is not None and (not isinstance(self.__misfire_grace_time, int) or self.__misfire_grace_time
|
|
147
|
+
if self.__misfire_grace_time is not None and (not isinstance(self.__misfire_grace_time, int) or self.__misfire_grace_time <= 0):
|
|
145
148
|
raise CLIOrionisValueError("Misfire grace time must be a positive integer or None.")
|
|
146
149
|
|
|
150
|
+
# Validate that coalesce is a boolean if it is set
|
|
151
|
+
if self.__coalesce is not None and not isinstance(self.__coalesce, bool):
|
|
152
|
+
raise CLIOrionisValueError("Coalesce must be a boolean value.")
|
|
153
|
+
|
|
147
154
|
# Construct and return an EventEntity with the current event's attributes
|
|
148
155
|
return EventEntity(
|
|
149
156
|
signature=self.__signature,
|
|
@@ -156,9 +163,39 @@ class Event(IEvent):
|
|
|
156
163
|
details=self.__details,
|
|
157
164
|
listener=self.__listener,
|
|
158
165
|
max_instances=self.__max_instances,
|
|
159
|
-
misfire_grace_time=self.__misfire_grace_time
|
|
166
|
+
misfire_grace_time=self.__misfire_grace_time,
|
|
167
|
+
coalesce=self.__coalesce
|
|
160
168
|
)
|
|
161
169
|
|
|
170
|
+
def coalesce(
|
|
171
|
+
self,
|
|
172
|
+
coalesce: bool = True
|
|
173
|
+
) -> 'Event':
|
|
174
|
+
"""
|
|
175
|
+
Set whether to coalesce missed event executions.
|
|
176
|
+
|
|
177
|
+
This method allows you to specify whether missed executions of the event
|
|
178
|
+
should be coalesced into a single execution when the scheduler is running
|
|
179
|
+
behind. If set to True, only the most recent missed execution will be run.
|
|
180
|
+
If set to False, all missed executions will be run in sequence.
|
|
181
|
+
|
|
182
|
+
Parameters
|
|
183
|
+
----------
|
|
184
|
+
coalesce : bool
|
|
185
|
+
A boolean indicating whether to coalesce missed executions. Defaults to True.
|
|
186
|
+
|
|
187
|
+
Returns
|
|
188
|
+
-------
|
|
189
|
+
Event
|
|
190
|
+
Returns the current instance of the Event to allow method chaining.
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
# Set the internal coalesce attribute
|
|
194
|
+
self.__coalesce = coalesce
|
|
195
|
+
|
|
196
|
+
# Return self to support method chaining
|
|
197
|
+
return self
|
|
198
|
+
|
|
162
199
|
def misfireGraceTime(
|
|
163
200
|
self,
|
|
164
201
|
seconds: int = 60
|
|
@@ -173,7 +210,7 @@ class Event(IEvent):
|
|
|
173
210
|
Parameters
|
|
174
211
|
----------
|
|
175
212
|
seconds : int
|
|
176
|
-
The number of seconds to allow for a misfire grace period. Must be a positive integer.
|
|
213
|
+
The number of seconds to allow for a misfire grace period. Must be a positive integer greater than zero.
|
|
177
214
|
|
|
178
215
|
Returns
|
|
179
216
|
-------
|
|
@@ -187,7 +224,7 @@ class Event(IEvent):
|
|
|
187
224
|
"""
|
|
188
225
|
|
|
189
226
|
# Validate that the seconds parameter is a positive integer
|
|
190
|
-
if not isinstance(seconds, int) or seconds
|
|
227
|
+
if not isinstance(seconds, int) or seconds <= 0:
|
|
191
228
|
raise CLIOrionisValueError("Misfire grace time must be a positive integer.")
|
|
192
229
|
|
|
193
230
|
# Set the internal misfire grace time attribute
|
|
@@ -448,9 +485,14 @@ class Event(IEvent):
|
|
|
448
485
|
if not isinstance(date, datetime):
|
|
449
486
|
raise CLIOrionisValueError("The date must be a datetime instance.")
|
|
450
487
|
|
|
488
|
+
# Ensure that random delay is not set for a one-time execution
|
|
489
|
+
if self.__random_delay > 0:
|
|
490
|
+
raise CLIOrionisValueError("Random delay cannot be applied to a one-time execution.")
|
|
491
|
+
|
|
451
492
|
# Set both start and end dates to the specified date for a one-time execution
|
|
452
493
|
self.__start_date = date
|
|
453
494
|
self.__end_date = date
|
|
495
|
+
self.__max_instances = 1
|
|
454
496
|
|
|
455
497
|
# Use a DateTrigger to schedule the event to run once at the specified date and time
|
|
456
498
|
self.__trigger = DateTrigger(run_date=date)
|
|
@@ -461,7 +503,7 @@ class Event(IEvent):
|
|
|
461
503
|
# Indicate that the scheduling was successful
|
|
462
504
|
return True
|
|
463
505
|
|
|
464
|
-
def
|
|
506
|
+
def everySeconds(
|
|
465
507
|
self,
|
|
466
508
|
seconds: int
|
|
467
509
|
) -> bool:
|
|
@@ -497,6 +539,10 @@ class Event(IEvent):
|
|
|
497
539
|
if not isinstance(seconds, int) or seconds <= 0:
|
|
498
540
|
raise CLIOrionisValueError(self.ERROR_MSG_INVALID_INTERVAL)
|
|
499
541
|
|
|
542
|
+
# Ensure that random delay is not set for second-based intervals.
|
|
543
|
+
if self.__random_delay > 0:
|
|
544
|
+
raise CLIOrionisValueError("Random delay (jitter) cannot be applied to second-based intervals.")
|
|
545
|
+
|
|
500
546
|
# Configure the trigger to execute the event at the specified interval,
|
|
501
547
|
# using any previously set start_date and end_date.
|
|
502
548
|
self.__trigger = IntervalTrigger(
|
|
@@ -536,7 +582,7 @@ class Event(IEvent):
|
|
|
536
582
|
"""
|
|
537
583
|
|
|
538
584
|
# Delegate scheduling to the everySecond method with an interval of 5 seconds.
|
|
539
|
-
return self.
|
|
585
|
+
return self.everySeconds(5)
|
|
540
586
|
|
|
541
587
|
def everyTenSeconds(
|
|
542
588
|
self
|
|
@@ -563,7 +609,7 @@ class Event(IEvent):
|
|
|
563
609
|
"""
|
|
564
610
|
|
|
565
611
|
# Delegate scheduling to the everySecond method with an interval of 10 seconds.
|
|
566
|
-
return self.
|
|
612
|
+
return self.everySeconds(10)
|
|
567
613
|
|
|
568
614
|
def everyFifteenSeconds(
|
|
569
615
|
self
|
|
@@ -590,7 +636,7 @@ class Event(IEvent):
|
|
|
590
636
|
"""
|
|
591
637
|
|
|
592
638
|
# Delegate scheduling to the everySecond method with an interval of 15 seconds.
|
|
593
|
-
return self.
|
|
639
|
+
return self.everySeconds(15)
|
|
594
640
|
|
|
595
641
|
def everyTwentySeconds(
|
|
596
642
|
self
|
|
@@ -613,7 +659,7 @@ class Event(IEvent):
|
|
|
613
659
|
"""
|
|
614
660
|
|
|
615
661
|
# Delegate scheduling to the everySecond method with an interval of 20 seconds.
|
|
616
|
-
return self.
|
|
662
|
+
return self.everySeconds(20)
|
|
617
663
|
|
|
618
664
|
def everyTwentyFiveSeconds(
|
|
619
665
|
self
|
|
@@ -640,7 +686,7 @@ class Event(IEvent):
|
|
|
640
686
|
"""
|
|
641
687
|
|
|
642
688
|
# Delegate scheduling to the everySecond method with an interval of 25 seconds.
|
|
643
|
-
return self.
|
|
689
|
+
return self.everySeconds(25)
|
|
644
690
|
|
|
645
691
|
def everyThirtySeconds(
|
|
646
692
|
self
|
|
@@ -667,7 +713,7 @@ class Event(IEvent):
|
|
|
667
713
|
"""
|
|
668
714
|
|
|
669
715
|
# Delegate scheduling to the everySecond method with an interval of 30 seconds.
|
|
670
|
-
return self.
|
|
716
|
+
return self.everySeconds(30)
|
|
671
717
|
|
|
672
718
|
def everyThirtyFiveSeconds(
|
|
673
719
|
self
|
|
@@ -690,7 +736,7 @@ class Event(IEvent):
|
|
|
690
736
|
"""
|
|
691
737
|
|
|
692
738
|
# Delegate scheduling to the everySecond method with an interval of 35 seconds.
|
|
693
|
-
return self.
|
|
739
|
+
return self.everySeconds(35)
|
|
694
740
|
|
|
695
741
|
def everyFortySeconds(
|
|
696
742
|
self
|
|
@@ -717,7 +763,7 @@ class Event(IEvent):
|
|
|
717
763
|
"""
|
|
718
764
|
|
|
719
765
|
# Delegate scheduling to the everySecond method with an interval of 40 seconds.
|
|
720
|
-
return self.
|
|
766
|
+
return self.everySeconds(40)
|
|
721
767
|
|
|
722
768
|
def everyFortyFiveSeconds(
|
|
723
769
|
self
|
|
@@ -744,7 +790,7 @@ class Event(IEvent):
|
|
|
744
790
|
"""
|
|
745
791
|
|
|
746
792
|
# Delegate scheduling to the everySecond method with an interval of 45 seconds.
|
|
747
|
-
return self.
|
|
793
|
+
return self.everySeconds(45)
|
|
748
794
|
|
|
749
795
|
def everyFiftySeconds(
|
|
750
796
|
self
|
|
@@ -771,7 +817,7 @@ class Event(IEvent):
|
|
|
771
817
|
"""
|
|
772
818
|
|
|
773
819
|
# Delegate scheduling to the everySecond method with an interval of 50 seconds.
|
|
774
|
-
return self.
|
|
820
|
+
return self.everySeconds(50)
|
|
775
821
|
|
|
776
822
|
def everyFiftyFiveSeconds(
|
|
777
823
|
self
|
|
@@ -794,7 +840,7 @@ class Event(IEvent):
|
|
|
794
840
|
"""
|
|
795
841
|
|
|
796
842
|
# Delegate scheduling to the everySecond method with an interval of 55 seconds.
|
|
797
|
-
return self.
|
|
843
|
+
return self.everySeconds(55)
|
|
798
844
|
|
|
799
845
|
def everyMinute(
|
|
800
846
|
self,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
from datetime import datetime
|
|
3
3
|
import logging
|
|
4
|
-
from typing import Any, Dict, List, Optional, Union
|
|
4
|
+
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
5
5
|
from apscheduler.events import (
|
|
6
6
|
EVENT_JOB_ERROR,
|
|
7
7
|
EVENT_JOB_EXECUTED,
|
|
@@ -15,6 +15,9 @@ from apscheduler.events import (
|
|
|
15
15
|
EVENT_SCHEDULER_STARTED,
|
|
16
16
|
)
|
|
17
17
|
from apscheduler.schedulers.asyncio import AsyncIOScheduler as APSAsyncIOScheduler
|
|
18
|
+
from apscheduler.schedulers.base import BaseScheduler
|
|
19
|
+
from apscheduler.schedulers.base import STATE_PAUSED, STATE_RUNNING
|
|
20
|
+
from apscheduler.triggers.date import DateTrigger
|
|
18
21
|
from rich.console import Console
|
|
19
22
|
from rich.panel import Panel
|
|
20
23
|
from rich.text import Text
|
|
@@ -77,7 +80,7 @@ class Schedule(ISchedule):
|
|
|
77
80
|
"""
|
|
78
81
|
|
|
79
82
|
# List of operations that can be performed on the scheduler
|
|
80
|
-
self.
|
|
83
|
+
self.__control_operations = [
|
|
81
84
|
'schedule:pause',
|
|
82
85
|
'schedule:resume',
|
|
83
86
|
'schedule:shutdown'
|
|
@@ -94,6 +97,7 @@ class Schedule(ISchedule):
|
|
|
94
97
|
|
|
95
98
|
# Initialize the AsyncIOScheduler with the configured timezone.
|
|
96
99
|
self.__scheduler = APSAsyncIOScheduler(timezone=self.__tz)
|
|
100
|
+
self.__control_scheduler = APSAsyncIOScheduler(timezone=self.__tz)
|
|
97
101
|
|
|
98
102
|
# Disable APScheduler's internal logging to avoid duplicate/conflicting logs.
|
|
99
103
|
self.__disableLogging()
|
|
@@ -116,15 +120,15 @@ class Schedule(ISchedule):
|
|
|
116
120
|
# Initialize the dictionary to manage event listeners.
|
|
117
121
|
self.__listeners: Dict[str, callable] = {}
|
|
118
122
|
|
|
119
|
-
# Initialize a set to track jobs paused by pauseEverythingAt.
|
|
120
|
-
self.__pausedByPauseEverything: set = set()
|
|
121
|
-
|
|
122
123
|
# Initialize the asyncio event used to signal scheduler shutdown.
|
|
123
124
|
self._stopEvent: Optional[asyncio.Event] = None
|
|
124
125
|
|
|
125
126
|
# Retrieve and initialize the catch instance for exception handling.
|
|
126
127
|
self.__catch: ICatch = app.make(ICatch)
|
|
127
128
|
|
|
129
|
+
# Initialize the paused at timestamp
|
|
130
|
+
self.__paused_at: Optional[datetime] = None
|
|
131
|
+
|
|
128
132
|
def __disableLogging(
|
|
129
133
|
self
|
|
130
134
|
) -> None:
|
|
@@ -211,11 +215,6 @@ class Schedule(ISchedule):
|
|
|
211
215
|
# Get the current time in the scheduler's configured timezone
|
|
212
216
|
now = datetime.now(self.__tz)
|
|
213
217
|
|
|
214
|
-
# Log the timezone currently assigned to the scheduler for traceability
|
|
215
|
-
self.__logger.info(
|
|
216
|
-
f"Timezone assigned to the scheduler: {self.__app.config('app.timezone') or 'UTC'}"
|
|
217
|
-
)
|
|
218
|
-
|
|
219
218
|
# Return the formatted current time string
|
|
220
219
|
return now.strftime("%Y-%m-%d %H:%M:%S")
|
|
221
220
|
|
|
@@ -489,7 +488,7 @@ class Schedule(ISchedule):
|
|
|
489
488
|
_misfire_grace_time = self.__getAttribute(data, 'misfire_grace_time', None)
|
|
490
489
|
|
|
491
490
|
# Extract the job max_instances if available
|
|
492
|
-
_max_instances = self.__getAttribute(data, 'max_instances',
|
|
491
|
+
_max_instances = self.__getAttribute(data, 'max_instances', 1)
|
|
493
492
|
|
|
494
493
|
# Extract the job coalesce if available
|
|
495
494
|
_coalesce = self.__getAttribute(data, 'coalesce', False)
|
|
@@ -775,6 +774,7 @@ class Schedule(ISchedule):
|
|
|
775
774
|
("\n\n", ""), # Add spacing
|
|
776
775
|
("The scheduled tasks worker has started successfully.\n", "white"), # Main message
|
|
777
776
|
(f"Started at: {now}\n", "dim"), # Display the start time in dim text
|
|
777
|
+
(f"Timezone: {self.__tz.key}\n", "dim"), # Display the configured timezone
|
|
778
778
|
("To stop the worker, press ", "white"), # Instruction text
|
|
779
779
|
("Ctrl+C", "bold yellow"), # Highlight the key combination
|
|
780
780
|
(".", "white") # End the instruction
|
|
@@ -800,6 +800,11 @@ class Schedule(ISchedule):
|
|
|
800
800
|
# Log an informational message indicating that the scheduler has started
|
|
801
801
|
self.__logger.info(f"Orionis Scheduler started successfully at: {now}.")
|
|
802
802
|
|
|
803
|
+
# Log the timezone currently assigned to the scheduler for traceability
|
|
804
|
+
self.__logger.info(
|
|
805
|
+
f"Timezone assigned to the scheduler: {self.__tz.key}."
|
|
806
|
+
)
|
|
807
|
+
|
|
803
808
|
def __shutdownListener(
|
|
804
809
|
self,
|
|
805
810
|
event
|
|
@@ -878,9 +883,6 @@ class Schedule(ISchedule):
|
|
|
878
883
|
event_exception = self.__getAttribute(event, 'exception', None)
|
|
879
884
|
event_traceback = self.__getAttribute(event, 'traceback', None)
|
|
880
885
|
|
|
881
|
-
# Log an error message indicating that the job raised an exception
|
|
882
|
-
self.__logger.error(f"Task '{event_id}' raised an exception: {event_exception}")
|
|
883
|
-
|
|
884
886
|
# Retrieve the job event data and update it with error details
|
|
885
887
|
job_event_data = self.__getTaskFromSchedulerById(event_id)
|
|
886
888
|
job_event_data.code = event_code
|
|
@@ -939,9 +941,6 @@ class Schedule(ISchedule):
|
|
|
939
941
|
event_id = self.__getAttribute(event, 'job_id', None)
|
|
940
942
|
event_code = self.__getAttribute(event, 'code', 0)
|
|
941
943
|
|
|
942
|
-
# Log an informational message indicating that the job has been submitted to the executor
|
|
943
|
-
self.__logger.info(f"Task '{event_id}' submitted to executor.")
|
|
944
|
-
|
|
945
944
|
# Create an event entity for the submitted job, including its ID and code
|
|
946
945
|
data_event = self.__getTaskFromSchedulerById(event_id, event_code)
|
|
947
946
|
|
|
@@ -983,9 +982,6 @@ class Schedule(ISchedule):
|
|
|
983
982
|
event_id = self.__getAttribute(event, 'job_id', None)
|
|
984
983
|
event_code = self.__getAttribute(event, 'code', 0)
|
|
985
984
|
|
|
986
|
-
# Log an informational message indicating that the job has been executed
|
|
987
|
-
self.__logger.info(f"Task '{event_id}' executed.")
|
|
988
|
-
|
|
989
985
|
# Create an event entity for the executed job, including its ID and code
|
|
990
986
|
data_event = self.__getTaskFromSchedulerById(event_id, event_code)
|
|
991
987
|
|
|
@@ -1025,15 +1021,9 @@ class Schedule(ISchedule):
|
|
|
1025
1021
|
|
|
1026
1022
|
# Extract the job ID from the event object, or None if not present
|
|
1027
1023
|
event_id = self.__getAttribute(event, 'job_id', None)
|
|
1024
|
+
|
|
1028
1025
|
# Extract the event code from the event object, defaulting to 0 if not present
|
|
1029
1026
|
event_code = self.__getAttribute(event, 'code', 0)
|
|
1030
|
-
# Extract the scheduled run time from the event object, or 'Unknown' if not present
|
|
1031
|
-
event_scheduled_run_time = self.__getAttribute(event, 'scheduled_run_time', 'Unknown')
|
|
1032
|
-
|
|
1033
|
-
# Log a warning indicating that the job was missed and when it was scheduled to run
|
|
1034
|
-
self.__logger.warning(
|
|
1035
|
-
f"Task '{event_id}' was missed. It was scheduled to run at: {event_scheduled_run_time}."
|
|
1036
|
-
)
|
|
1037
1027
|
|
|
1038
1028
|
# Create an event entity for the missed job, including its ID and code
|
|
1039
1029
|
data_event = self.__getTaskFromSchedulerById(event_id, event_code)
|
|
@@ -1077,9 +1067,6 @@ class Schedule(ISchedule):
|
|
|
1077
1067
|
event_id = self.__getAttribute(event, 'job_id', None)
|
|
1078
1068
|
event_code = self.__getAttribute(event, 'code', 0)
|
|
1079
1069
|
|
|
1080
|
-
# Log an error message indicating that the job exceeded maximum concurrent instances
|
|
1081
|
-
self.__logger.error(f"Task '{event_id}' exceeded maximum instances")
|
|
1082
|
-
|
|
1083
1070
|
# Create an event entity for the job that exceeded max instances
|
|
1084
1071
|
data_event = self.__getTaskFromSchedulerById(event_id, event_code)
|
|
1085
1072
|
|
|
@@ -1120,27 +1107,109 @@ class Schedule(ISchedule):
|
|
|
1120
1107
|
event_id = self.__getAttribute(event, 'job_id', None)
|
|
1121
1108
|
event_code = self.__getAttribute(event, 'code', 0)
|
|
1122
1109
|
|
|
1123
|
-
# Log the removal of the job
|
|
1124
|
-
self.__logger.info(f"Task '{event_id}' has been removed.")
|
|
1125
|
-
|
|
1126
1110
|
# Create an event entity for the removed job
|
|
1127
1111
|
data_event = self.__getTaskFromSchedulerById(event_id, event_code)
|
|
1128
1112
|
|
|
1129
1113
|
# If a listener is registered for this job ID, invoke the listener with the event details
|
|
1130
1114
|
self.__taskCallableListener(data_event, ListeningEvent.JOB_ON_REMOVED)
|
|
1131
1115
|
|
|
1116
|
+
def __triggerIsPast(
|
|
1117
|
+
self,
|
|
1118
|
+
signature: str,
|
|
1119
|
+
entity: EventEntity
|
|
1120
|
+
) -> bool:
|
|
1121
|
+
"""
|
|
1122
|
+
Determine if the trigger for a scheduled event is set to a date in the past.
|
|
1123
|
+
|
|
1124
|
+
This method checks whether the trigger associated with the given event entity is a
|
|
1125
|
+
`DateTrigger` and, if so, compares its `run_date` to the current time in the scheduler's
|
|
1126
|
+
configured timezone. If the trigger's run date is in the past, a warning is logged and
|
|
1127
|
+
the method returns True, indicating that the event should be skipped.
|
|
1128
|
+
|
|
1129
|
+
Parameters
|
|
1130
|
+
----------
|
|
1131
|
+
signature : str
|
|
1132
|
+
The unique signature identifying the scheduled event.
|
|
1133
|
+
entity : EventEntity
|
|
1134
|
+
The event entity containing the trigger and scheduling information.
|
|
1135
|
+
|
|
1136
|
+
Returns
|
|
1137
|
+
-------
|
|
1138
|
+
bool
|
|
1139
|
+
True if the event's trigger is a `DateTrigger` with a run date in the past; False otherwise.
|
|
1140
|
+
|
|
1141
|
+
Notes
|
|
1142
|
+
-----
|
|
1143
|
+
This method is used to prevent scheduling events that would immediately be considered expired.
|
|
1144
|
+
"""
|
|
1145
|
+
|
|
1146
|
+
# Check if the event's trigger is a DateTrigger and its run_date is in the past
|
|
1147
|
+
if isinstance(entity.trigger, DateTrigger):
|
|
1148
|
+
run_date = getattr(entity.trigger, 'run_date', None)
|
|
1149
|
+
now = self.__getNow()
|
|
1150
|
+
|
|
1151
|
+
# If the run_date is set and is before the current time, log a warning and return True
|
|
1152
|
+
if run_date is not None and run_date < now:
|
|
1153
|
+
self.__logger.warning(
|
|
1154
|
+
f"Scheduled event '{signature}' has a run_date in the past ({run_date}); skipping."
|
|
1155
|
+
)
|
|
1156
|
+
return True
|
|
1157
|
+
|
|
1158
|
+
# Return False if the trigger is not a past DateTrigger
|
|
1159
|
+
return False
|
|
1160
|
+
|
|
1161
|
+
def __eventToEntity(
|
|
1162
|
+
self,
|
|
1163
|
+
event
|
|
1164
|
+
) -> EventEntity:
|
|
1165
|
+
"""
|
|
1166
|
+
Convert a scheduled event object to its corresponding EventEntity representation.
|
|
1167
|
+
|
|
1168
|
+
This method takes a scheduled event and transforms it into an `EventEntity` instance,
|
|
1169
|
+
which encapsulates all relevant attributes required for internal tracking and management
|
|
1170
|
+
within the scheduler. The conversion relies on the event implementing a `toEntity` method.
|
|
1171
|
+
|
|
1172
|
+
Parameters
|
|
1173
|
+
----------
|
|
1174
|
+
event : Any
|
|
1175
|
+
The scheduled event object to be converted. Must implement a `toEntity` method.
|
|
1176
|
+
|
|
1177
|
+
Returns
|
|
1178
|
+
-------
|
|
1179
|
+
EventEntity
|
|
1180
|
+
An instance of `EventEntity` containing the extracted attributes from the event.
|
|
1181
|
+
|
|
1182
|
+
Raises
|
|
1183
|
+
------
|
|
1184
|
+
CLIOrionisValueError
|
|
1185
|
+
If the provided event does not implement a `toEntity` method.
|
|
1186
|
+
|
|
1187
|
+
Notes
|
|
1188
|
+
-----
|
|
1189
|
+
This method is used internally to standardize event objects for consistent handling
|
|
1190
|
+
within the scheduler's job management system.
|
|
1191
|
+
"""
|
|
1192
|
+
|
|
1193
|
+
# Ensure the event has a toEntity method for conversion
|
|
1194
|
+
if not hasattr(event, 'toEntity'):
|
|
1195
|
+
raise CLIOrionisValueError("The event must have a 'toEntity' method for conversion.")
|
|
1196
|
+
|
|
1197
|
+
# Convert the event to its entity representation (EventEntity)
|
|
1198
|
+
to_entity = getattr(event, 'toEntity')
|
|
1199
|
+
return to_entity()
|
|
1200
|
+
|
|
1132
1201
|
def __loadEvents(
|
|
1133
1202
|
self
|
|
1134
1203
|
) -> None:
|
|
1135
1204
|
"""
|
|
1136
|
-
|
|
1205
|
+
Synchronize and register all scheduled events with the AsyncIOScheduler.
|
|
1137
1206
|
|
|
1138
|
-
This method
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1207
|
+
This method updates the internal jobs list (`self.__jobs`) by converting each event in the internal
|
|
1208
|
+
events dictionary to its corresponding entity representation. It then schedules these jobs in the
|
|
1209
|
+
appropriate scheduler (control or main) based on their signature. Events with triggers set in the
|
|
1210
|
+
past are skipped to prevent immediate expiration. Associated listeners are registered for each job
|
|
1211
|
+
as needed. This ensures that all scheduled jobs are properly tracked and managed by both the internal
|
|
1212
|
+
state and the scheduler.
|
|
1144
1213
|
|
|
1145
1214
|
Parameters
|
|
1146
1215
|
----------
|
|
@@ -1149,65 +1218,51 @@ class Schedule(ISchedule):
|
|
|
1149
1218
|
Returns
|
|
1150
1219
|
-------
|
|
1151
1220
|
None
|
|
1152
|
-
This method does not return any value. It updates the internal jobs list and
|
|
1153
|
-
|
|
1221
|
+
This method does not return any value. It updates the internal jobs list and registers jobs
|
|
1222
|
+
with the AsyncIOScheduler.
|
|
1154
1223
|
|
|
1155
1224
|
Raises
|
|
1156
1225
|
------
|
|
1157
1226
|
CLIOrionisRuntimeError
|
|
1158
|
-
|
|
1159
|
-
with a descriptive message.
|
|
1227
|
+
Raised if an error occurs while loading or scheduling an event.
|
|
1160
1228
|
|
|
1161
1229
|
Notes
|
|
1162
1230
|
-----
|
|
1163
|
-
- Events are only
|
|
1164
|
-
- Each event must implement a `toEntity` method
|
|
1165
|
-
- Jobs are added to the scheduler with their respective configuration, and listeners
|
|
1166
|
-
are registered if present.
|
|
1231
|
+
- Events are loaded only if the internal jobs list is empty to avoid duplicate scheduling.
|
|
1232
|
+
- Each event must implement a `toEntity` method for conversion.
|
|
1233
|
+
- Jobs are added to the scheduler with their respective configuration, and listeners are registered if present.
|
|
1167
1234
|
"""
|
|
1168
1235
|
|
|
1169
1236
|
# Only load events if the jobs list is empty to avoid duplicate scheduling
|
|
1170
1237
|
if not self.__jobs:
|
|
1171
1238
|
|
|
1239
|
+
# Lists to separate control jobs and custom jobs
|
|
1240
|
+
control_jobs = []
|
|
1241
|
+
custom_jobs = []
|
|
1242
|
+
|
|
1172
1243
|
# Iterate through all scheduled events in the internal events dictionary
|
|
1173
1244
|
for signature, event in self.__events.items():
|
|
1174
1245
|
|
|
1175
1246
|
try:
|
|
1176
1247
|
|
|
1177
|
-
# Ensure the event has a toEntity method for conversion
|
|
1178
|
-
if not hasattr(event, 'toEntity'):
|
|
1179
|
-
continue
|
|
1180
|
-
|
|
1181
1248
|
# Convert the event to its entity representation (EventEntity)
|
|
1182
|
-
|
|
1183
|
-
|
|
1249
|
+
entity = self.__eventToEntity(event)
|
|
1250
|
+
|
|
1251
|
+
# Skip loading events with a DateTrigger set in the past
|
|
1252
|
+
if self.__triggerIsPast(signature, entity):
|
|
1253
|
+
continue
|
|
1184
1254
|
|
|
1185
1255
|
# Add the job entity to the internal jobs list
|
|
1186
1256
|
self.__jobs.append(entity)
|
|
1187
1257
|
|
|
1188
|
-
#
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
self.__scheduler.add_job(
|
|
1194
|
-
func=create_job_func(signature, list(entity.args)),
|
|
1195
|
-
trigger=entity.trigger,
|
|
1196
|
-
id=signature,
|
|
1197
|
-
name=signature,
|
|
1198
|
-
replace_existing=True,
|
|
1199
|
-
max_instances=entity.max_instances,
|
|
1200
|
-
misfire_grace_time=entity.misfire_grace_time
|
|
1201
|
-
)
|
|
1202
|
-
|
|
1203
|
-
# If the event entity has an associated listener, register it
|
|
1204
|
-
if entity.listener:
|
|
1205
|
-
self.setListener(signature, entity.listener)
|
|
1206
|
-
|
|
1207
|
-
# Log the successful loading of the scheduled event for debugging
|
|
1208
|
-
self.__logger.debug(f"Scheduled event '{signature}' loaded successfully.")
|
|
1258
|
+
# Determine which scheduler to use based on the job signature
|
|
1259
|
+
if signature in self.__control_operations:
|
|
1260
|
+
control_jobs.append((signature, entity))
|
|
1261
|
+
else:
|
|
1262
|
+
custom_jobs.append((signature, entity))
|
|
1209
1263
|
|
|
1210
1264
|
except Exception as e:
|
|
1265
|
+
|
|
1211
1266
|
# Construct an error message for failed event loading
|
|
1212
1267
|
error_msg = f"Failed to load scheduled event '{signature}': {str(e)}"
|
|
1213
1268
|
|
|
@@ -1217,6 +1272,72 @@ class Schedule(ISchedule):
|
|
|
1217
1272
|
# Raise a runtime error to signal failure in loading the scheduled event
|
|
1218
1273
|
raise CLIOrionisRuntimeError(error_msg)
|
|
1219
1274
|
|
|
1275
|
+
# Register control jobs with the control scheduler
|
|
1276
|
+
self.__loadJobs(self.__control_scheduler, control_jobs)
|
|
1277
|
+
|
|
1278
|
+
# Register custom jobs with the main scheduler
|
|
1279
|
+
self.__loadJobs(self.__scheduler, custom_jobs)
|
|
1280
|
+
|
|
1281
|
+
# Log the successful loading of the scheduled events for debugging
|
|
1282
|
+
self.__logger.debug(f"Scheduled events loaded successfully: {len(self.__jobs)} jobs.")
|
|
1283
|
+
|
|
1284
|
+
def __loadJobs(
|
|
1285
|
+
self,
|
|
1286
|
+
scheduler: BaseScheduler,
|
|
1287
|
+
events: List[Tuple[str, EventEntity]]
|
|
1288
|
+
) -> None:
|
|
1289
|
+
"""
|
|
1290
|
+
Load and register jobs into the specified scheduler.
|
|
1291
|
+
|
|
1292
|
+
This method iterates over a list of event tuples, each containing a job signature and its corresponding
|
|
1293
|
+
EventEntity. For each event, it creates a job function that invokes the reactor with the provided command
|
|
1294
|
+
and arguments, then adds the job to the given scheduler with the appropriate configuration. If the event
|
|
1295
|
+
entity has an associated listener, the listener is registered for the job.
|
|
1296
|
+
|
|
1297
|
+
Parameters
|
|
1298
|
+
----------
|
|
1299
|
+
scheduler : BaseScheduler
|
|
1300
|
+
The scheduler instance (either control or main) where jobs will be registered.
|
|
1301
|
+
events : List[Tuple[str, EventEntity]]
|
|
1302
|
+
A list of tuples, each containing a job signature and its corresponding EventEntity.
|
|
1303
|
+
|
|
1304
|
+
Returns
|
|
1305
|
+
-------
|
|
1306
|
+
None
|
|
1307
|
+
This method does not return any value. It registers jobs and listeners with the scheduler.
|
|
1308
|
+
|
|
1309
|
+
Notes
|
|
1310
|
+
-----
|
|
1311
|
+
- The job function is created as a lambda that calls the reactor with the command and arguments.
|
|
1312
|
+
- Jobs are added with configuration options such as trigger, ID, name, max instances, misfire grace time, and coalesce.
|
|
1313
|
+
- If an event entity has a listener, it is registered using the setListener method.
|
|
1314
|
+
"""
|
|
1315
|
+
|
|
1316
|
+
# Helper function to create a job function that calls the reactor with the given command and arguments
|
|
1317
|
+
def create_job_func(cmd, args_list):
|
|
1318
|
+
|
|
1319
|
+
# Returns a lambda that executes the reactor call for the specified command and arguments
|
|
1320
|
+
return lambda: self.__reactor.call(cmd, args_list)
|
|
1321
|
+
|
|
1322
|
+
# Iterate over each event tuple and add the job to the scheduler
|
|
1323
|
+
for signature, entity in events:
|
|
1324
|
+
|
|
1325
|
+
# Add the job to the scheduler with the specified configuration
|
|
1326
|
+
scheduler.add_job(
|
|
1327
|
+
func=create_job_func(signature, list(entity.args)),
|
|
1328
|
+
trigger=entity.trigger,
|
|
1329
|
+
id=signature,
|
|
1330
|
+
name=f"{signature}({', '.join(map(str, entity.args))})",
|
|
1331
|
+
replace_existing=True,
|
|
1332
|
+
max_instances=entity.max_instances,
|
|
1333
|
+
misfire_grace_time=entity.misfire_grace_time,
|
|
1334
|
+
coalesce=entity.coalesce
|
|
1335
|
+
)
|
|
1336
|
+
|
|
1337
|
+
# If the event entity has an associated listener, register it
|
|
1338
|
+
if entity.listener:
|
|
1339
|
+
self.setListener(signature, entity.listener)
|
|
1340
|
+
|
|
1220
1341
|
def __raiseException(
|
|
1221
1342
|
self,
|
|
1222
1343
|
exception: BaseException
|
|
@@ -1319,7 +1440,6 @@ class Schedule(ISchedule):
|
|
|
1319
1440
|
|
|
1320
1441
|
# Register the listener for the specified event in the internal listeners dictionary
|
|
1321
1442
|
self.__listeners[event] = listener
|
|
1322
|
-
print(self.__listeners)
|
|
1323
1443
|
|
|
1324
1444
|
def pause(
|
|
1325
1445
|
self
|
|
@@ -1350,9 +1470,6 @@ class Schedule(ISchedule):
|
|
|
1350
1470
|
# Only pause jobs if the scheduler is currently running
|
|
1351
1471
|
if self.isRunning():
|
|
1352
1472
|
|
|
1353
|
-
# Clear the set of previously paused jobs to avoid stale entries
|
|
1354
|
-
self.__pausedByPauseEverything.clear()
|
|
1355
|
-
|
|
1356
1473
|
# Retrieve all jobs currently managed by the scheduler
|
|
1357
1474
|
all_jobs = self.__scheduler.get_jobs()
|
|
1358
1475
|
|
|
@@ -1360,22 +1477,14 @@ class Schedule(ISchedule):
|
|
|
1360
1477
|
for job in all_jobs:
|
|
1361
1478
|
|
|
1362
1479
|
try:
|
|
1480
|
+
|
|
1363
1481
|
# Get the job ID safely
|
|
1364
1482
|
job_id = self.__getAttribute(job, 'id', None)
|
|
1365
1483
|
|
|
1366
1484
|
# Skip jobs without a valid user-defined ID (ignore system/operation jobs)
|
|
1367
|
-
if not job_id or not isinstance(job_id, str) or job_id.strip() == ""
|
|
1485
|
+
if not job_id or not isinstance(job_id, str) or job_id.strip() == "":
|
|
1368
1486
|
continue
|
|
1369
1487
|
|
|
1370
|
-
# Pause the job in the scheduler
|
|
1371
|
-
self.__scheduler.pause_job(job_id)
|
|
1372
|
-
|
|
1373
|
-
# Track the paused job's ID
|
|
1374
|
-
self.__pausedByPauseEverything.add(job_id)
|
|
1375
|
-
|
|
1376
|
-
# Get the current time in the configured timezone for logging
|
|
1377
|
-
now = self.__getCurrentTime()
|
|
1378
|
-
|
|
1379
1488
|
# Retrieve event data for the paused job
|
|
1380
1489
|
event_data = self.__getTaskFromSchedulerById(job_id)
|
|
1381
1490
|
|
|
@@ -1385,9 +1494,6 @@ class Schedule(ISchedule):
|
|
|
1385
1494
|
ListeningEvent.JOB_ON_PAUSED
|
|
1386
1495
|
)
|
|
1387
1496
|
|
|
1388
|
-
# Log the pause action for this job
|
|
1389
|
-
self.__logger.info(f"Task '{job_id}' paused successfully at {now}.")
|
|
1390
|
-
|
|
1391
1497
|
except Exception as e:
|
|
1392
1498
|
|
|
1393
1499
|
# Handle any errors that occur while pausing a job
|
|
@@ -1399,8 +1505,17 @@ class Schedule(ISchedule):
|
|
|
1399
1505
|
time=self.__getNow()
|
|
1400
1506
|
), ListeningEvent.SCHEDULER_PAUSED)
|
|
1401
1507
|
|
|
1508
|
+
# Get the current time in the configured timezone for logging
|
|
1509
|
+
now = self.__getCurrentTime()
|
|
1510
|
+
|
|
1511
|
+
# Clear the set of previously paused jobs to avoid stale entries
|
|
1512
|
+
self.__scheduler.pause()
|
|
1513
|
+
|
|
1514
|
+
# Store the timestamp when the scheduler was paused
|
|
1515
|
+
self.__paused_at = self.__getNow()
|
|
1516
|
+
|
|
1402
1517
|
# Log that all tasks have been paused
|
|
1403
|
-
self.__logger.info("
|
|
1518
|
+
self.__logger.info(f"Orionis Scheduler paused all tasks successfully at: {now}.")
|
|
1404
1519
|
|
|
1405
1520
|
def resume(
|
|
1406
1521
|
self
|
|
@@ -1429,55 +1544,44 @@ class Schedule(ISchedule):
|
|
|
1429
1544
|
"""
|
|
1430
1545
|
|
|
1431
1546
|
# Only resume jobs if the scheduler is currently running
|
|
1432
|
-
if self.
|
|
1433
|
-
|
|
1434
|
-
# Resume only jobs that were paused by the pause method
|
|
1435
|
-
if self.__pausedByPauseEverything:
|
|
1436
|
-
|
|
1437
|
-
# Iterate through the set of paused job IDs and resume each one
|
|
1438
|
-
for job_id in self.__pausedByPauseEverything:
|
|
1439
|
-
|
|
1440
|
-
try:
|
|
1441
|
-
|
|
1442
|
-
# Resume the job in the scheduler
|
|
1443
|
-
self.__scheduler.resume_job(job_id)
|
|
1444
|
-
|
|
1445
|
-
# Retrieve event data for the resumed job
|
|
1446
|
-
event_data = self.__getTaskFromSchedulerById(job_id)
|
|
1547
|
+
if self.isPaused():
|
|
1447
1548
|
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1549
|
+
# Execute the global callable listener after all jobs are resumed
|
|
1550
|
+
self.__globalCallableListener(SchedulerResumed(
|
|
1551
|
+
code=EVENT_SCHEDULER_RESUMED,
|
|
1552
|
+
time=self.__getNow()
|
|
1553
|
+
), ListeningEvent.SCHEDULER_RESUMED)
|
|
1453
1554
|
|
|
1454
|
-
|
|
1455
|
-
|
|
1555
|
+
# Get the current time in the configured timezone for logging
|
|
1556
|
+
now = self.__getCurrentTime()
|
|
1456
1557
|
|
|
1457
|
-
|
|
1558
|
+
# Cuando resumes:
|
|
1559
|
+
resumed_at = self.__getNow()
|
|
1458
1560
|
|
|
1459
|
-
|
|
1460
|
-
|
|
1561
|
+
# Resume main scheduler
|
|
1562
|
+
self.__scheduler.resume()
|
|
1461
1563
|
|
|
1462
|
-
|
|
1463
|
-
|
|
1564
|
+
# Iterate through all jobs to check for missed executions
|
|
1565
|
+
for job in self.__scheduler.get_jobs():
|
|
1464
1566
|
|
|
1465
|
-
#
|
|
1466
|
-
self.
|
|
1467
|
-
code=EVENT_SCHEDULER_RESUMED,
|
|
1468
|
-
time=self.__getNow()
|
|
1469
|
-
), ListeningEvent.SCHEDULER_RESUMED)
|
|
1567
|
+
# If the job was supposed to run while paused, trigger the missed event
|
|
1568
|
+
if job.next_run_time and self.__paused_at <= job.next_run_time < resumed_at:
|
|
1470
1569
|
|
|
1471
|
-
|
|
1472
|
-
|
|
1570
|
+
# Retrieve event data for the missed job
|
|
1571
|
+
event_data = self.__getTaskFromSchedulerById(job.id)
|
|
1473
1572
|
|
|
1474
|
-
|
|
1475
|
-
|
|
1573
|
+
# Invoke the listener for the missed job event
|
|
1574
|
+
self.__taskCallableListener(
|
|
1575
|
+
event_data,
|
|
1576
|
+
ListeningEvent.JOB_ON_MISSED
|
|
1577
|
+
)
|
|
1476
1578
|
|
|
1477
|
-
|
|
1478
|
-
|
|
1579
|
+
# Log an informational message indicating that the scheduler has been resumed
|
|
1580
|
+
self.__logger.info(f"Orionis Scheduler resumed all tasks successfully at: {now}.")
|
|
1479
1581
|
|
|
1480
|
-
async def start(
|
|
1582
|
+
async def start(
|
|
1583
|
+
self
|
|
1584
|
+
) -> None:
|
|
1481
1585
|
"""
|
|
1482
1586
|
Start the AsyncIO scheduler instance and keep it running.
|
|
1483
1587
|
|
|
@@ -1514,6 +1618,7 @@ class Schedule(ISchedule):
|
|
|
1514
1618
|
# Start the scheduler if it is not already running
|
|
1515
1619
|
if not self.isRunning():
|
|
1516
1620
|
self.__scheduler.start()
|
|
1621
|
+
self.__control_scheduler.start()
|
|
1517
1622
|
|
|
1518
1623
|
# Log that the scheduler is now active and waiting for events
|
|
1519
1624
|
self.__logger.info("Orionis Scheduler is now active and waiting for events...")
|
|
@@ -1553,7 +1658,10 @@ class Schedule(ISchedule):
|
|
|
1553
1658
|
# Raise a runtime error for any other issues during startup
|
|
1554
1659
|
raise CLIOrionisRuntimeError(f"Failed to start the scheduler: {str(e)}") from e
|
|
1555
1660
|
|
|
1556
|
-
async def shutdown(
|
|
1661
|
+
async def shutdown(
|
|
1662
|
+
self,
|
|
1663
|
+
wait: bool = True
|
|
1664
|
+
) -> None:
|
|
1557
1665
|
"""
|
|
1558
1666
|
Shut down the AsyncIO scheduler instance asynchronously.
|
|
1559
1667
|
|
|
@@ -1587,15 +1695,13 @@ class Schedule(ISchedule):
|
|
|
1587
1695
|
|
|
1588
1696
|
# If the scheduler is not running, there's nothing to shut down
|
|
1589
1697
|
if not self.isRunning():
|
|
1590
|
-
self.__logger.info("The scheduler is already stopped. No shutdown action is required.")
|
|
1591
1698
|
return
|
|
1592
1699
|
|
|
1593
1700
|
try:
|
|
1594
|
-
# Log the shutdown process
|
|
1595
|
-
self.__logger.info(f"Starting Orionis Scheduler shutdown process (wait={wait})...")
|
|
1596
1701
|
|
|
1597
1702
|
# Shut down the AsyncIOScheduler
|
|
1598
1703
|
self.__scheduler.shutdown(wait=wait)
|
|
1704
|
+
self.__control_scheduler.shutdown(wait=wait)
|
|
1599
1705
|
|
|
1600
1706
|
# Signal the stop event to break the wait in start()
|
|
1601
1707
|
if self._stop_event and not self._stop_event.is_set():
|
|
@@ -1605,10 +1711,14 @@ class Schedule(ISchedule):
|
|
|
1605
1711
|
if wait:
|
|
1606
1712
|
await asyncio.sleep(0.1)
|
|
1607
1713
|
|
|
1714
|
+
# Get the current time in the configured timezone for logging
|
|
1715
|
+
now = self.__getCurrentTime()
|
|
1716
|
+
|
|
1608
1717
|
# Log the successful shutdown
|
|
1609
|
-
self.__logger.info("Orionis Scheduler has been shut down successfully.")
|
|
1718
|
+
self.__logger.info(f"Orionis Scheduler has been shut down successfully at: {now}.")
|
|
1610
1719
|
|
|
1611
1720
|
except Exception as e:
|
|
1721
|
+
|
|
1612
1722
|
# Handle exceptions that may occur during shutdown
|
|
1613
1723
|
self.__raiseException(
|
|
1614
1724
|
CLIOrionisRuntimeError(
|
|
@@ -1616,7 +1726,10 @@ class Schedule(ISchedule):
|
|
|
1616
1726
|
)
|
|
1617
1727
|
)
|
|
1618
1728
|
|
|
1619
|
-
def
|
|
1729
|
+
def pauseCommand(
|
|
1730
|
+
self,
|
|
1731
|
+
signature: str
|
|
1732
|
+
) -> bool:
|
|
1620
1733
|
"""
|
|
1621
1734
|
Pause a scheduled job in the AsyncIO scheduler.
|
|
1622
1735
|
|
|
@@ -1652,9 +1765,19 @@ class Schedule(ISchedule):
|
|
|
1652
1765
|
self.__raiseException(CLIOrionisValueError(self.SIGNATURE_REQUIRED_ERROR))
|
|
1653
1766
|
|
|
1654
1767
|
try:
|
|
1768
|
+
|
|
1655
1769
|
# Attempt to pause the job with the given signature
|
|
1656
1770
|
self.__scheduler.pause_job(signature)
|
|
1657
1771
|
|
|
1772
|
+
# Retrieve event data for the paused job
|
|
1773
|
+
event_data = self.__getTaskFromSchedulerById(signature)
|
|
1774
|
+
|
|
1775
|
+
# Invoke the listener for the paused job event
|
|
1776
|
+
self.__taskCallableListener(
|
|
1777
|
+
event_data,
|
|
1778
|
+
ListeningEvent.JOB_ON_PAUSED
|
|
1779
|
+
)
|
|
1780
|
+
|
|
1658
1781
|
# Log the successful pausing of the job
|
|
1659
1782
|
self.__logger.info(f"Pause '{signature}' has been paused.")
|
|
1660
1783
|
|
|
@@ -1666,7 +1789,10 @@ class Schedule(ISchedule):
|
|
|
1666
1789
|
# Return False if the job could not be paused (e.g., it does not exist or another error occurred)
|
|
1667
1790
|
return False
|
|
1668
1791
|
|
|
1669
|
-
def
|
|
1792
|
+
def resumeCommand(
|
|
1793
|
+
self,
|
|
1794
|
+
signature: str
|
|
1795
|
+
) -> bool:
|
|
1670
1796
|
"""
|
|
1671
1797
|
Resume a paused job in the AsyncIO scheduler.
|
|
1672
1798
|
|
|
@@ -1706,6 +1832,15 @@ class Schedule(ISchedule):
|
|
|
1706
1832
|
# Attempt to resume the job with the given signature
|
|
1707
1833
|
self.__scheduler.resume_job(signature)
|
|
1708
1834
|
|
|
1835
|
+
# Retrieve event data for the resumed job
|
|
1836
|
+
event_data = self.__getTaskFromSchedulerById(signature)
|
|
1837
|
+
|
|
1838
|
+
# Invoke the listener for the resumed job event
|
|
1839
|
+
self.__taskCallableListener(
|
|
1840
|
+
event_data,
|
|
1841
|
+
ListeningEvent.JOB_ON_RESUMED
|
|
1842
|
+
)
|
|
1843
|
+
|
|
1709
1844
|
# Log the successful resumption of the job
|
|
1710
1845
|
self.__logger.info(f"Task '{signature}' has been resumed.")
|
|
1711
1846
|
|
|
@@ -1717,7 +1852,10 @@ class Schedule(ISchedule):
|
|
|
1717
1852
|
# Return False if the job could not be resumed (e.g., it does not exist or another error occurred)
|
|
1718
1853
|
return False
|
|
1719
1854
|
|
|
1720
|
-
def
|
|
1855
|
+
def removeCommand(
|
|
1856
|
+
self,
|
|
1857
|
+
signature: str
|
|
1858
|
+
) -> bool:
|
|
1721
1859
|
"""
|
|
1722
1860
|
Remove a scheduled job from the AsyncIO scheduler by its signature.
|
|
1723
1861
|
|
|
@@ -1775,7 +1913,9 @@ class Schedule(ISchedule):
|
|
|
1775
1913
|
# Return False if the job could not be removed (e.g., it does not exist or another error occurred)
|
|
1776
1914
|
return False
|
|
1777
1915
|
|
|
1778
|
-
def events(
|
|
1916
|
+
def events(
|
|
1917
|
+
self
|
|
1918
|
+
) -> List[Dict]:
|
|
1779
1919
|
"""
|
|
1780
1920
|
Retrieve a list of all scheduled jobs managed by the scheduler.
|
|
1781
1921
|
|
|
@@ -1909,7 +2049,9 @@ class Schedule(ISchedule):
|
|
|
1909
2049
|
# Return None if no job with the given signature is found
|
|
1910
2050
|
return None
|
|
1911
2051
|
|
|
1912
|
-
def isRunning(
|
|
2052
|
+
def isRunning(
|
|
2053
|
+
self
|
|
2054
|
+
) -> bool:
|
|
1913
2055
|
"""
|
|
1914
2056
|
Check if the scheduler is currently running.
|
|
1915
2057
|
|
|
@@ -1924,9 +2066,11 @@ class Schedule(ISchedule):
|
|
|
1924
2066
|
"""
|
|
1925
2067
|
|
|
1926
2068
|
# Return True if the scheduler is running, otherwise False
|
|
1927
|
-
return self.__scheduler.running
|
|
2069
|
+
return self.__scheduler.running and self.__scheduler.state == STATE_RUNNING
|
|
1928
2070
|
|
|
1929
|
-
def isPaused(
|
|
2071
|
+
def isPaused(
|
|
2072
|
+
self
|
|
2073
|
+
) -> bool:
|
|
1930
2074
|
"""
|
|
1931
2075
|
Check if the scheduler is currently paused.
|
|
1932
2076
|
|
|
@@ -1942,9 +2086,11 @@ class Schedule(ISchedule):
|
|
|
1942
2086
|
"""
|
|
1943
2087
|
|
|
1944
2088
|
# The scheduler is considered paused if there are any jobs in the paused set
|
|
1945
|
-
return
|
|
2089
|
+
return self.__scheduler.running and self.__scheduler.state == STATE_PAUSED
|
|
1946
2090
|
|
|
1947
|
-
def forceStop(
|
|
2091
|
+
def forceStop(
|
|
2092
|
+
self
|
|
2093
|
+
) -> None:
|
|
1948
2094
|
"""
|
|
1949
2095
|
Forcefully stop the scheduler immediately, bypassing graceful shutdown.
|
|
1950
2096
|
|
|
@@ -1969,12 +2115,15 @@ class Schedule(ISchedule):
|
|
|
1969
2115
|
# If the scheduler is currently running, shut it down immediately without waiting for jobs to finish
|
|
1970
2116
|
if self.__scheduler.running:
|
|
1971
2117
|
self.__scheduler.shutdown(wait=False)
|
|
2118
|
+
self.__control_scheduler.shutdown(wait=False)
|
|
1972
2119
|
|
|
1973
2120
|
# If the stop event exists and has not already been set, signal it to interrupt the main loop
|
|
1974
2121
|
if self._stop_event and not self._stop_event.is_set():
|
|
1975
2122
|
self._stop_event.set()
|
|
1976
2123
|
|
|
1977
|
-
def stop(
|
|
2124
|
+
def stop(
|
|
2125
|
+
self
|
|
2126
|
+
) -> None:
|
|
1978
2127
|
"""
|
|
1979
2128
|
Signal the scheduler to stop synchronously by setting the internal stop event.
|
|
1980
2129
|
|
orionis/metadata/framework.py
CHANGED
|
@@ -15,7 +15,7 @@ orionis/console/commands/help.py,sha256=VFIn3UqQm4pxwjfSQohVwKNpc7F-6XRcwSZQwDSL
|
|
|
15
15
|
orionis/console/commands/log_clear.py,sha256=OI1j_myCYUOMI-SfnN-NH-6BYzzWKXOKIEb55vFTXq4,4045
|
|
16
16
|
orionis/console/commands/make_command.py,sha256=WHE2J78fuY9RHScCOn9R_2KjkiayYJHFlm-Mp-k8X-o,5956
|
|
17
17
|
orionis/console/commands/scheduler_list.py,sha256=7Ug0eoqusIOsmHaiakm3-JYAK8VNIypOtKMcSzn-AL0,4544
|
|
18
|
-
orionis/console/commands/scheduler_work.py,sha256=
|
|
18
|
+
orionis/console/commands/scheduler_work.py,sha256=dDW9XTjGb-5ABuhPYjfd1rfO7xLDhgYu5nd6nToiYFE,5597
|
|
19
19
|
orionis/console/commands/test.py,sha256=G3pveONRbeWTcVwaHat3wTAhxMm4g_BwhJhIejq6V0Y,3518
|
|
20
20
|
orionis/console/commands/version.py,sha256=u5_8CfnEVdS3VSME8rbP6o3Z0XFZ30nSz8uHdahBAoY,4766
|
|
21
21
|
orionis/console/contracts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -25,12 +25,12 @@ orionis/console/contracts/cli_request.py,sha256=Q5eiOitdZve-33e5dqO_O-Fha5I1u4u7
|
|
|
25
25
|
orionis/console/contracts/command.py,sha256=Wvm62hw2gDIPNmoEUFyhKUERjZ6wnAbI-UDY_5gJE24,3626
|
|
26
26
|
orionis/console/contracts/console.py,sha256=TuoyCLInhp5ztlyJ82rBsw_1Ki3_fOnF_gtaWUxSW1M,14253
|
|
27
27
|
orionis/console/contracts/dumper.py,sha256=h8ScHF31Q3oOdEe5_EyozOD8OGcLvkMG4KkJwa3ANX8,4538
|
|
28
|
-
orionis/console/contracts/event.py,sha256=
|
|
28
|
+
orionis/console/contracts/event.py,sha256=cdnao4tqVhIr24X1uFzFfzZ4wdN1OZcKS1sKcX33abU,119843
|
|
29
29
|
orionis/console/contracts/executor.py,sha256=JAzK64_5HfIGm3_BwsUv7-ZeC2Y-3mpxOByHaXwuy9A,4278
|
|
30
30
|
orionis/console/contracts/kernel.py,sha256=mh4LlhEYHh3FuGZZQ0GBhD6ZLa5YQvaNj2r01IIHI5Y,826
|
|
31
31
|
orionis/console/contracts/progress_bar.py,sha256=NYebL2h-vg2t2H6IhJjuC37gglRkpT-MW71wbJtpLNg,1784
|
|
32
32
|
orionis/console/contracts/reactor.py,sha256=iT6ShoCutAWEeJzOf_PK7CGXi9TgrOD5tewHFVQ2NQw,6075
|
|
33
|
-
orionis/console/contracts/schedule.py,sha256=
|
|
33
|
+
orionis/console/contracts/schedule.py,sha256=NaVOUpuE08X5rm9-oXyNs6r3LbOg7B_rIz-k4r7y4Qk,13703
|
|
34
34
|
orionis/console/contracts/schedule_event_listener.py,sha256=h06qsBxuEMD3KLSyu0JXdUDHlQW19BX9lA09Qrh2QXg,3818
|
|
35
35
|
orionis/console/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
36
36
|
orionis/console/core/reactor.py,sha256=qctLns-f5eB9Air7Qi4hvX5KB5A7SsHeV8M5zYJEiPA,44286
|
|
@@ -40,7 +40,7 @@ orionis/console/dynamic/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG
|
|
|
40
40
|
orionis/console/dynamic/progress_bar.py,sha256=58wne7E_QZb_uN9jjqQ_V28Ci19SVNhCQQ4zzXCiOu0,2872
|
|
41
41
|
orionis/console/entities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
42
42
|
orionis/console/entities/command.py,sha256=NPdyG4U3JtDMOS7Em2_AklNyyuoTnKxyGseMWrADlc4,1571
|
|
43
|
-
orionis/console/entities/event.py,sha256=
|
|
43
|
+
orionis/console/entities/event.py,sha256=ri7BVjBhpPlxUt-gmJObuhB2gqVQ-vTXUyPy2iv2Aj0,3101
|
|
44
44
|
orionis/console/entities/event_job.py,sha256=b8fbloHnf7qYtQ0W4d7MWw0jj4l07B0RtS3UkMJwE0M,4292
|
|
45
45
|
orionis/console/entities/scheduler_error.py,sha256=VzwSK8DloX-tFlBLWX93sEV4oO0Qo9rheNjiOEjdNyE,1413
|
|
46
46
|
orionis/console/entities/scheduler_event_data.py,sha256=s9zYvLYO4O91l_9Qncx688QjcsjrIjXI8xsXqK2ZlHI,785
|
|
@@ -56,7 +56,7 @@ orionis/console/exceptions/__init__.py,sha256=rY9PE85TO_HpI5cfGWbZSDzk5aOUjErqBZ
|
|
|
56
56
|
orionis/console/exceptions/cli_exceptions.py,sha256=nk3EGkRheDbw8vrxKgUxkAAPA6gPpO2dE5nDeVHJHA0,3580
|
|
57
57
|
orionis/console/fluent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
58
58
|
orionis/console/fluent/command.py,sha256=0jyhB45LIjDS7nkjNhkkvEXEzdOBmT49qKIooYY_DbI,8261
|
|
59
|
-
orionis/console/fluent/event.py,sha256=
|
|
59
|
+
orionis/console/fluent/event.py,sha256=lw0lYRhHxyflA7JE4ZLye6rjEOGUmLFMO9e9btwWfNM,168532
|
|
60
60
|
orionis/console/output/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
61
61
|
orionis/console/output/console.py,sha256=xRZXSZiVqlyc1L16MBUV1M3TUHQwrWSFYUtisivm8U8,24423
|
|
62
62
|
orionis/console/output/executor.py,sha256=uQjFPOlyZLFj9pcyYPugCqxwJog0AJgK1OcmQH2ELbw,7314
|
|
@@ -65,7 +65,7 @@ orionis/console/request/cli_request.py,sha256=sH7Q2MpMIasiPiEPBeGhExnbfpSic98vQd
|
|
|
65
65
|
orionis/console/stubs/command.stub,sha256=ABQ2eFxJ0u0DvTfmoO_DdECkBy-rie-woG62G2Gxw8Y,805
|
|
66
66
|
orionis/console/stubs/listener.stub,sha256=DbX-ghx2-vb73kzQ6L20lyg5vSKn58jSLTwFuwNQU9k,4331
|
|
67
67
|
orionis/console/tasks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
68
|
-
orionis/console/tasks/schedule.py,sha256=
|
|
68
|
+
orionis/console/tasks/schedule.py,sha256=R2mkjp7zFSnhlaBWDrdzcQsHmj8cdOUNyx2_73tzWx0,92762
|
|
69
69
|
orionis/container/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
70
70
|
orionis/container/container.py,sha256=LaGFSzDH2YjmWzdiV-r8Z9xs9fyFGJnsanRsEStqHp8,114033
|
|
71
71
|
orionis/container/context/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -207,7 +207,7 @@ orionis/foundation/providers/scheduler_provider.py,sha256=IrPQJwvQVLRm5Qnz0Cxon4
|
|
|
207
207
|
orionis/foundation/providers/testing_provider.py,sha256=eI1p2lUlxl25b5Z487O4nmqLE31CTDb4c3Q21xFadkE,1615
|
|
208
208
|
orionis/foundation/providers/workers_provider.py,sha256=GdHENYV_yGyqmHJHn0DCyWmWId5xWjD48e6Zq2PGCWY,1674
|
|
209
209
|
orionis/metadata/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
210
|
-
orionis/metadata/framework.py,sha256=
|
|
210
|
+
orionis/metadata/framework.py,sha256=nfWN_AFm6d2eQFvlJzxl1uswTO-wwXT7brAA27gKiEw,4720
|
|
211
211
|
orionis/metadata/package.py,sha256=s1JeGJPwdVh4jO3IOfmpwMuJ_oX6Vf9NL7jgPEQNf5Y,16050
|
|
212
212
|
orionis/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
213
213
|
orionis/services/asynchrony/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -402,8 +402,8 @@ orionis/test/validators/workers.py,sha256=HcZ3cnrk6u7cvM1xZpn_lsglHAq69_jx9RcTSv
|
|
|
402
402
|
orionis/test/view/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
403
403
|
orionis/test/view/render.py,sha256=arysoswhkV2vUd2aVMZRPpmH317jaWbgjDpQ_AWQ5AE,5663
|
|
404
404
|
orionis/test/view/report.stub,sha256=QLqqCdRoENr3ECiritRB3DO_MOjRQvgBh5jxZ3Hs1r0,28189
|
|
405
|
-
orionis-0.
|
|
406
|
-
orionis-0.
|
|
407
|
-
orionis-0.
|
|
408
|
-
orionis-0.
|
|
409
|
-
orionis-0.
|
|
405
|
+
orionis-0.727.0.dist-info/licenses/LICENCE,sha256=JhC-z_9mbpUrCfPjcl3DhDA8trNDMzb57cvRSam1avc,1463
|
|
406
|
+
orionis-0.727.0.dist-info/METADATA,sha256=SqjKGk5oqSuIgDXwexcEWVR_ie_IARUEVjM9OgmCsus,4962
|
|
407
|
+
orionis-0.727.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
408
|
+
orionis-0.727.0.dist-info/top_level.txt,sha256=lyXi6jArpqJ-0zzNqd_uwsH-z9TCEBVBL-pC3Ekv7hU,8
|
|
409
|
+
orionis-0.727.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|