orionis 0.478.0__py3-none-any.whl → 0.480.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 +88 -0
- orionis/console/commands/test.py +16 -9
- orionis/console/contracts/event.py +178 -0
- orionis/console/contracts/schedule.py +124 -1
- orionis/console/core/reactor.py +5 -3
- orionis/console/enums/event.py +55 -0
- orionis/console/kernel.py +1 -1
- orionis/console/tasks/event.py +332 -0
- orionis/console/tasks/exception_report.py +94 -0
- orionis/console/tasks/schedule.py +162 -266
- orionis/container/container.py +33 -22
- orionis/container/facades/facade.py +3 -2
- orionis/foundation/application.py +8 -3
- orionis/foundation/config/roots/paths.py +0 -1
- orionis/foundation/contracts/application.py +2 -2
- orionis/foundation/providers/scheduler_provider.py +51 -0
- orionis/metadata/framework.py +1 -1
- orionis/test/core/unit_test.py +2 -3
- orionis/test/kernel.py +1 -1
- {orionis-0.478.0.dist-info → orionis-0.480.0.dist-info}/METADATA +1 -1
- {orionis-0.478.0.dist-info → orionis-0.480.0.dist-info}/RECORD +26 -21
- tests/testing/test_testing_unit.py +8 -7
- orionis/console/enums/task.py +0 -42
- {orionis-0.478.0.dist-info → orionis-0.480.0.dist-info}/WHEEL +0 -0
- {orionis-0.478.0.dist-info → orionis-0.480.0.dist-info}/licenses/LICENCE +0 -0
- {orionis-0.478.0.dist-info → orionis-0.480.0.dist-info}/top_level.txt +0 -0
- {orionis-0.478.0.dist-info → orionis-0.480.0.dist-info}/zip-safe +0 -0
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import random
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from typing import List, Optional, Union
|
|
4
|
+
from apscheduler.triggers.cron import CronTrigger
|
|
5
|
+
from apscheduler.triggers.date import DateTrigger
|
|
6
|
+
from apscheduler.triggers.interval import IntervalTrigger
|
|
7
|
+
from orionis.console.contracts.event import IEvent
|
|
8
|
+
from orionis.console.enums.event import Event as EventEntity
|
|
9
|
+
from orionis.console.exceptions import CLIOrionisValueError
|
|
10
|
+
|
|
11
|
+
class Event(IEvent):
|
|
12
|
+
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
signature: str,
|
|
16
|
+
args: Optional[List[str]],
|
|
17
|
+
purpose: Optional[str] = None,
|
|
18
|
+
):
|
|
19
|
+
"""
|
|
20
|
+
Initialize a new Event instance.
|
|
21
|
+
|
|
22
|
+
Parameters
|
|
23
|
+
----------
|
|
24
|
+
signature : str
|
|
25
|
+
The unique identifier or signature for the event. This is typically
|
|
26
|
+
used to distinguish between different events in the system.
|
|
27
|
+
args : Optional[List[str]]
|
|
28
|
+
A list of arguments required by the event. If not provided, an empty
|
|
29
|
+
list will be used.
|
|
30
|
+
purpose : Optional[str], optional
|
|
31
|
+
A human-readable description or purpose for the event, by default None.
|
|
32
|
+
|
|
33
|
+
Returns
|
|
34
|
+
-------
|
|
35
|
+
None
|
|
36
|
+
This constructor does not return any value. It initializes the internal
|
|
37
|
+
state of the Event object.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
# Store the event's signature
|
|
41
|
+
self.__signature: str = signature
|
|
42
|
+
|
|
43
|
+
# Store the event's arguments, defaulting to an empty list if None is provided
|
|
44
|
+
self.__args: Optional[List[str]] = args if args is not None else []
|
|
45
|
+
|
|
46
|
+
# Store the event's purpose or description
|
|
47
|
+
self.__purpose: Optional[str] = purpose
|
|
48
|
+
|
|
49
|
+
# Initialize the random delay attribute (in seconds) as None
|
|
50
|
+
self.__random_delay: Optional[int] = None
|
|
51
|
+
|
|
52
|
+
# Initialize the start date for the event as None
|
|
53
|
+
self.__start_date: Optional[datetime] = None
|
|
54
|
+
|
|
55
|
+
# Initialize the end date for the event as None
|
|
56
|
+
self.__end_date: Optional[datetime] = None
|
|
57
|
+
|
|
58
|
+
# Initialize the trigger for the event as None; can be set to a Cron, Date, or Interval trigger
|
|
59
|
+
self.__trigger: Optional[Union[CronTrigger, DateTrigger, IntervalTrigger]] = None
|
|
60
|
+
|
|
61
|
+
# Initialize the details for the event as None; can be used to store additional information
|
|
62
|
+
self.__details: Optional[str] = None
|
|
63
|
+
|
|
64
|
+
def toEntity(
|
|
65
|
+
self
|
|
66
|
+
) -> EventEntity:
|
|
67
|
+
"""
|
|
68
|
+
Retrieve the event details as an EventEntity instance.
|
|
69
|
+
|
|
70
|
+
This method gathers all relevant attributes of the current Event object,
|
|
71
|
+
such as its signature, arguments, purpose, random delay, start and end dates,
|
|
72
|
+
and trigger, and returns them encapsulated in an EventEntity object.
|
|
73
|
+
|
|
74
|
+
Returns
|
|
75
|
+
-------
|
|
76
|
+
EventEntity
|
|
77
|
+
An EventEntity instance containing the event's signature, arguments,
|
|
78
|
+
purpose, random delay, start date, end date, and trigger.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
# Validate that the signature is set and is a non-empty string
|
|
82
|
+
if not self.__signature:
|
|
83
|
+
raise CLIOrionisValueError("Signature is required for the event.")
|
|
84
|
+
if not isinstance(self.__args, list):
|
|
85
|
+
raise CLIOrionisValueError("Args must be a list.")
|
|
86
|
+
if self.__purpose is not None and not isinstance(self.__purpose, str):
|
|
87
|
+
raise CLIOrionisValueError("Purpose must be a string or None.")
|
|
88
|
+
|
|
89
|
+
# Validate that start_date and end_date are datetime instances if they are set
|
|
90
|
+
if self.__start_date is not None and not isinstance(self.__start_date, datetime):
|
|
91
|
+
raise CLIOrionisValueError("Start date must be a datetime instance.")
|
|
92
|
+
if self.__end_date is not None and not isinstance(self.__end_date, datetime):
|
|
93
|
+
raise CLIOrionisValueError("End date must be a datetime instance.")
|
|
94
|
+
|
|
95
|
+
# Validate that trigger is one of the expected types if it is set
|
|
96
|
+
if self.__trigger is not None and not isinstance(self.__trigger, (CronTrigger, DateTrigger, IntervalTrigger)):
|
|
97
|
+
raise CLIOrionisValueError("Trigger must be a CronTrigger, DateTrigger, or IntervalTrigger.")
|
|
98
|
+
|
|
99
|
+
# Validate that random_delay is an integer if it is set
|
|
100
|
+
if self.__random_delay is not None and not isinstance(self.__random_delay, int):
|
|
101
|
+
raise CLIOrionisValueError("Random delay must be an integer or None.")
|
|
102
|
+
|
|
103
|
+
# Validate that details is a string if it is set
|
|
104
|
+
if self.__details is not None and not isinstance(self.__details, str):
|
|
105
|
+
raise CLIOrionisValueError("Details must be a string or None.")
|
|
106
|
+
|
|
107
|
+
# Construct and return an EventEntity with the current event's attributes
|
|
108
|
+
return EventEntity(
|
|
109
|
+
signature=self.__signature,
|
|
110
|
+
args=self.__args,
|
|
111
|
+
purpose=self.__purpose,
|
|
112
|
+
random_delay=self.__random_delay,
|
|
113
|
+
start_date=self.__start_date,
|
|
114
|
+
end_date=self.__end_date,
|
|
115
|
+
trigger=self.__trigger,
|
|
116
|
+
details=self.__details
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
def purpose(
|
|
120
|
+
self,
|
|
121
|
+
purpose: str
|
|
122
|
+
) -> 'Event':
|
|
123
|
+
"""
|
|
124
|
+
Set the purpose or description for the scheduled command.
|
|
125
|
+
|
|
126
|
+
This method assigns a human-readable purpose or description to the command
|
|
127
|
+
that is being scheduled. The purpose must be a non-empty string. This can
|
|
128
|
+
be useful for documentation, logging, or displaying information about the
|
|
129
|
+
scheduled job.
|
|
130
|
+
|
|
131
|
+
Parameters
|
|
132
|
+
----------
|
|
133
|
+
purpose : str
|
|
134
|
+
The purpose or description to associate with the scheduled command.
|
|
135
|
+
Must be a non-empty string.
|
|
136
|
+
|
|
137
|
+
Returns
|
|
138
|
+
-------
|
|
139
|
+
Scheduler
|
|
140
|
+
Returns the current instance of the Scheduler to allow method chaining.
|
|
141
|
+
|
|
142
|
+
Raises
|
|
143
|
+
------
|
|
144
|
+
CLIOrionisValueError
|
|
145
|
+
If the provided purpose is not a non-empty string.
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
# Validate that the purpose is a non-empty string
|
|
149
|
+
if not isinstance(purpose, str) or not purpose.strip():
|
|
150
|
+
raise CLIOrionisValueError("The purpose must be a non-empty string.")
|
|
151
|
+
|
|
152
|
+
# Set the internal purpose attribute
|
|
153
|
+
self.__purpose = purpose
|
|
154
|
+
|
|
155
|
+
# Return self to support method chaining
|
|
156
|
+
return self
|
|
157
|
+
|
|
158
|
+
def startDate(
|
|
159
|
+
self,
|
|
160
|
+
start_date: datetime
|
|
161
|
+
) -> 'Event':
|
|
162
|
+
"""
|
|
163
|
+
Set the start date for the event execution.
|
|
164
|
+
|
|
165
|
+
This method allows you to specify a start date for when the event should
|
|
166
|
+
begin execution. The start date must be a datetime instance.
|
|
167
|
+
|
|
168
|
+
Parameters
|
|
169
|
+
----------
|
|
170
|
+
start_date : datetime
|
|
171
|
+
The start date for the event execution.
|
|
172
|
+
|
|
173
|
+
Returns
|
|
174
|
+
-------
|
|
175
|
+
Event
|
|
176
|
+
Returns the current instance of the Event to allow method chaining.
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
# Validate that start_date is a datetime instance
|
|
180
|
+
if not isinstance(start_date, datetime):
|
|
181
|
+
raise CLIOrionisValueError("Start date must be a datetime instance.")
|
|
182
|
+
|
|
183
|
+
# Set the internal start date attribute
|
|
184
|
+
self.__start_date = start_date
|
|
185
|
+
|
|
186
|
+
# Return self to support method chaining
|
|
187
|
+
return self
|
|
188
|
+
|
|
189
|
+
def endDate(
|
|
190
|
+
self,
|
|
191
|
+
end_date: datetime
|
|
192
|
+
) -> 'Event':
|
|
193
|
+
"""
|
|
194
|
+
Set the end date for the event execution.
|
|
195
|
+
|
|
196
|
+
This method allows you to specify an end date for when the event should
|
|
197
|
+
stop executing. The end date must be a datetime instance.
|
|
198
|
+
|
|
199
|
+
Parameters
|
|
200
|
+
----------
|
|
201
|
+
end_date : datetime
|
|
202
|
+
The end date for the event execution.
|
|
203
|
+
|
|
204
|
+
Returns
|
|
205
|
+
-------
|
|
206
|
+
Event
|
|
207
|
+
Returns the current instance of the Event to allow method chaining.
|
|
208
|
+
"""
|
|
209
|
+
|
|
210
|
+
# Validate that end_date is a datetime instance
|
|
211
|
+
if not isinstance(end_date, datetime):
|
|
212
|
+
raise CLIOrionisValueError("End date must be a datetime instance.")
|
|
213
|
+
|
|
214
|
+
# Set the internal end date attribute
|
|
215
|
+
self.__end_date = end_date
|
|
216
|
+
|
|
217
|
+
# Return self to support method chaining
|
|
218
|
+
return self
|
|
219
|
+
|
|
220
|
+
def randomDelay(
|
|
221
|
+
self,
|
|
222
|
+
max_seconds: int = 10
|
|
223
|
+
) -> 'Event':
|
|
224
|
+
"""
|
|
225
|
+
Set a random delay for the event execution.
|
|
226
|
+
|
|
227
|
+
This method allows you to specify a random delay up to a maximum
|
|
228
|
+
number of seconds before the event is executed. This can be useful for
|
|
229
|
+
distributing load or avoiding collisions in scheduled tasks.
|
|
230
|
+
|
|
231
|
+
Parameters
|
|
232
|
+
----------
|
|
233
|
+
max_seconds : int
|
|
234
|
+
The maximum number of seconds to wait before executing the event.
|
|
235
|
+
|
|
236
|
+
Returns
|
|
237
|
+
-------
|
|
238
|
+
Event
|
|
239
|
+
Returns the current instance of the Event to allow method chaining.
|
|
240
|
+
"""
|
|
241
|
+
|
|
242
|
+
# Validate that max_seconds is a positive integer
|
|
243
|
+
if not isinstance(max_seconds, int) or max_seconds <= 0:
|
|
244
|
+
raise CLIOrionisValueError("max_seconds must be a positive integer.")
|
|
245
|
+
|
|
246
|
+
# Generate a random delay between 1 and max_seconds (inclusive)
|
|
247
|
+
self.__random_delay = random.randint(1, max_seconds)
|
|
248
|
+
|
|
249
|
+
# Return self to support method chaining
|
|
250
|
+
return self
|
|
251
|
+
|
|
252
|
+
def onceAt(
|
|
253
|
+
self,
|
|
254
|
+
date: datetime
|
|
255
|
+
) -> bool:
|
|
256
|
+
"""
|
|
257
|
+
Schedule the event to run once at a specific date and time.
|
|
258
|
+
|
|
259
|
+
This method allows you to schedule the event to execute once at a specified
|
|
260
|
+
date and time. The date must be a datetime instance.
|
|
261
|
+
|
|
262
|
+
Parameters
|
|
263
|
+
----------
|
|
264
|
+
date : datetime
|
|
265
|
+
The specific date and time when the event should run.
|
|
266
|
+
|
|
267
|
+
Returns
|
|
268
|
+
-------
|
|
269
|
+
bool
|
|
270
|
+
Returns True if the scheduling was successful.
|
|
271
|
+
"""
|
|
272
|
+
|
|
273
|
+
# Validate that date is a datetime instance
|
|
274
|
+
if not isinstance(date, datetime):
|
|
275
|
+
raise CLIOrionisValueError("The date must be a datetime instance.")
|
|
276
|
+
|
|
277
|
+
# Set the start and end dates to the specified date
|
|
278
|
+
self.__start_date = date
|
|
279
|
+
self.__end_date = date
|
|
280
|
+
|
|
281
|
+
# Set the trigger to a DateTrigger for the specified date
|
|
282
|
+
self.__trigger = DateTrigger(run_date=date)
|
|
283
|
+
|
|
284
|
+
# Optionally, set the details for the event
|
|
285
|
+
self.__details = f"Once At: {date.strftime('%Y-%m-%d %H:%M:%S')}"
|
|
286
|
+
|
|
287
|
+
# Return self to support method chaining
|
|
288
|
+
return True
|
|
289
|
+
|
|
290
|
+
def everySeconds(
|
|
291
|
+
self,
|
|
292
|
+
seconds: int
|
|
293
|
+
) -> bool:
|
|
294
|
+
"""
|
|
295
|
+
Schedule the event to run at fixed intervals measured in seconds.
|
|
296
|
+
|
|
297
|
+
This method configures the event to execute repeatedly at a specified interval
|
|
298
|
+
(in seconds). Optionally, the event can be restricted to a time window using
|
|
299
|
+
previously set start and end dates. A random delay (jitter) can also be applied
|
|
300
|
+
if configured.
|
|
301
|
+
|
|
302
|
+
Parameters
|
|
303
|
+
----------
|
|
304
|
+
seconds : int
|
|
305
|
+
The interval, in seconds, at which the event should be executed. Must be a positive integer.
|
|
306
|
+
|
|
307
|
+
Returns
|
|
308
|
+
-------
|
|
309
|
+
bool
|
|
310
|
+
Returns True if the interval scheduling was successfully configured.
|
|
311
|
+
|
|
312
|
+
Raises
|
|
313
|
+
------
|
|
314
|
+
CLIOrionisValueError
|
|
315
|
+
If `seconds` is not a positive integer.
|
|
316
|
+
"""
|
|
317
|
+
|
|
318
|
+
# Validate that the seconds parameter is a positive integer.
|
|
319
|
+
if not isinstance(seconds, int) or seconds <= 0:
|
|
320
|
+
raise CLIOrionisValueError("The interval must be a positive integer.")
|
|
321
|
+
|
|
322
|
+
# Configure the trigger to execute the event at the specified interval,
|
|
323
|
+
# using any previously set start_date, end_date, and random_delay (jitter).
|
|
324
|
+
self.__trigger = IntervalTrigger(
|
|
325
|
+
seconds=seconds,
|
|
326
|
+
start_date=self.__start_date,
|
|
327
|
+
end_date=self.__end_date,
|
|
328
|
+
jitter=self.__random_delay
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
# Indicate that the scheduling was successful.
|
|
332
|
+
return True
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
from rich.console import Console
|
|
2
|
+
from rich.panel import Panel
|
|
3
|
+
from rich.text import Text
|
|
4
|
+
from rich.traceback import Traceback
|
|
5
|
+
|
|
6
|
+
class ScheduleErrorReporter:
|
|
7
|
+
"""Handles and displays errors and warnings with rich formatting using the Rich library.
|
|
8
|
+
|
|
9
|
+
This class provides methods to output formatted error and warning messages to the console,
|
|
10
|
+
enhancing readability and debugging using the Rich library's features.
|
|
11
|
+
|
|
12
|
+
Attributes
|
|
13
|
+
----------
|
|
14
|
+
console : Console
|
|
15
|
+
Rich Console object used for rendering formatted output to the terminal.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self):
|
|
19
|
+
"""
|
|
20
|
+
Initialize the ErrorReporter instance.
|
|
21
|
+
|
|
22
|
+
This constructor creates a new Console object from the Rich library and assigns it to the 'console' attribute,
|
|
23
|
+
which is used for rendering formatted output to the terminal.
|
|
24
|
+
|
|
25
|
+
Returns
|
|
26
|
+
-------
|
|
27
|
+
None
|
|
28
|
+
This constructor does not return any value.
|
|
29
|
+
"""
|
|
30
|
+
self.console = Console()
|
|
31
|
+
|
|
32
|
+
def reportException(self, job_id: str, exception: Exception, traceback: str = None):
|
|
33
|
+
"""
|
|
34
|
+
Display a formatted error message for an exception that occurred during a job execution.
|
|
35
|
+
|
|
36
|
+
This method prints a visually enhanced error panel to the console, including the job identifier and the exception message.
|
|
37
|
+
If a traceback string is provided, it is rendered below the error panel for detailed debugging information.
|
|
38
|
+
|
|
39
|
+
Parameters
|
|
40
|
+
----------
|
|
41
|
+
job_id : str
|
|
42
|
+
The identifier of the job where the exception occurred.
|
|
43
|
+
exception : Exception
|
|
44
|
+
The exception instance that was raised.
|
|
45
|
+
traceback : str, optional
|
|
46
|
+
The string representation of the traceback. If provided, it will be displayed after the error message.
|
|
47
|
+
|
|
48
|
+
Returns
|
|
49
|
+
-------
|
|
50
|
+
None
|
|
51
|
+
This method does not return any value. It outputs formatted error information to the console.
|
|
52
|
+
"""
|
|
53
|
+
title = f"[bold red]Job Execution Error[/bold red]"
|
|
54
|
+
message = Text.assemble(
|
|
55
|
+
("An exception occurred during the execution of job ", "white"),
|
|
56
|
+
(f"'{job_id}'", "bold cyan"),
|
|
57
|
+
(":\n", "white"),
|
|
58
|
+
(f"{type(exception).__name__}: {exception}", "red")
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
self.console.print(Panel(message, title=title, border_style="red", padding=(1, 2)))
|
|
62
|
+
if traceback:
|
|
63
|
+
tb = Traceback.from_string(traceback)
|
|
64
|
+
self.console.print(tb)
|
|
65
|
+
self.console.line()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def reportMissed(self, job_id: str, scheduled_time):
|
|
69
|
+
"""
|
|
70
|
+
Display a formatted warning message for a missed job execution.
|
|
71
|
+
|
|
72
|
+
This method prints a warning panel to the console, indicating that a scheduled job was missed and showing its scheduled time.
|
|
73
|
+
|
|
74
|
+
Parameters
|
|
75
|
+
----------
|
|
76
|
+
job_id : str
|
|
77
|
+
The identifier of the missed job.
|
|
78
|
+
scheduled_time : Any
|
|
79
|
+
The time the job was scheduled to run.
|
|
80
|
+
|
|
81
|
+
Returns
|
|
82
|
+
-------
|
|
83
|
+
None
|
|
84
|
+
This method does not return any value. It outputs a warning message to the console.
|
|
85
|
+
"""
|
|
86
|
+
title = f"[bold yellow]Missed Scheduled Job[/bold yellow]"
|
|
87
|
+
msg = Text.assemble(
|
|
88
|
+
("The scheduled job ", "white"),
|
|
89
|
+
(f"'{job_id}'", "bold cyan"),
|
|
90
|
+
(" was not executed as planned.\nScheduled time: ", "white"),
|
|
91
|
+
(f"{scheduled_time}", "bold green")
|
|
92
|
+
)
|
|
93
|
+
self.console.print(Panel(msg, title=title, border_style="yellow", padding=(1, 2)))
|
|
94
|
+
self.console.line()
|