orionis 0.469.0__py3-none-any.whl → 0.470.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.
@@ -8,200 +8,696 @@ from apscheduler.triggers.interval import IntervalTrigger
8
8
  from orionis.console.contracts.reactor import IReactor
9
9
  from datetime import datetime
10
10
  import pytz
11
+ import asyncio
12
+ from typing import Union
11
13
  from orionis.console.exceptions import CLIOrionisRuntimeError
12
- from orionis.foundation.contracts.application import IApplication
13
14
  from orionis.app import Orionis
14
15
 
15
- class Scheduler:
16
- """
17
- Scheduler class to manage scheduled tasks in Orionis.
18
- This class allows you to define commands, set triggers, and manage the scheduling of tasks.
19
- """
16
+ class Scheduler():
20
17
 
21
- def __init__(self) -> None:
22
- self.__command: str = None
23
- self.__args: List[str] = None
24
- self.__type: str = None
25
- self.__purpose: str = None
26
- self.__mode: str = None
27
- self.__start_date: str = None
28
- self.__end_date: str = None
18
+ def __init__(
19
+ self,
20
+ reactor: IReactor
21
+ ) -> None:
22
+ """
23
+ Initialize a new instance of the Scheduler class.
24
+
25
+ This constructor sets up the internal state required for scheduling commands,
26
+ including references to the application instance, APScheduler schedulers, the
27
+ command reactor, and job tracking structures. It also initializes properties
28
+ for managing the current scheduling context.
29
+
30
+ Parameters
31
+ ----------
32
+ reactor : IReactor
33
+ An instance of a class implementing the IReactor interface, used to
34
+ retrieve available commands and execute scheduled jobs.
35
+
36
+ Returns
37
+ -------
38
+ None
39
+ This method does not return any value. It initializes the Scheduler instance.
40
+ """
41
+
42
+ # Store the application instance for configuration access.
43
+ self.__app = Orionis()
44
+
45
+ # Initialize scheduler instances (will be set up later).
46
+ self.__background_scheduler: APSBackgroundScheduler = None
47
+ self.__blocking_scheduler: APSBlockingScheduler = None
48
+ self.__asyncio_scheduler: APSAsyncIOScheduler = None
49
+ self.__initScheduler()
50
+
51
+ # Store the reactor instance for command management.
52
+ self.__reactor = reactor
53
+
54
+ # Retrieve and store all available commands from the reactor.
55
+ self.__available_commands = self.__getCommands()
56
+
57
+ # Dictionary to hold all scheduled jobs and their details.
58
+ self.__jobs: dict = {}
59
+
60
+ # Properties to track the current scheduling context.
61
+ self.__command: str = None # The command signature to be scheduled.
62
+ self.__args: List[str] = None # Arguments for the command.
63
+ self.__purpose: str = None # Purpose or description of the scheduled job.
64
+ self.__type: str = None # Scheduler type (background, blocking, asyncio).
29
65
 
30
- def command(self, signature: str, args: Optional[List[str]] = None) -> 'Scheduler':
66
+ def __initScheduler(
67
+ self
68
+ ) -> None:
31
69
  """
32
- Set the command signature and its arguments.
70
+ Initialize the internal APScheduler instances for background, blocking, and asyncio scheduling.
71
+
72
+ This method creates and configures three types of schedulers:
73
+ - BackgroundScheduler: Runs jobs in the background using threads.
74
+ - BlockingScheduler: Runs jobs in the foreground and blocks the main thread.
75
+ - AsyncIOScheduler: Integrates with asyncio event loops for asynchronous job execution.
76
+
77
+ The timezone for all schedulers is set based on the application's configuration.
78
+
79
+ Returns
80
+ -------
81
+ None
82
+ This method does not return any value. It initializes internal scheduler attributes.
33
83
  """
34
- if not isinstance(signature, str) or not signature.strip():
35
- raise ValueError("The command signature must be a non-empty string.")
36
84
 
37
- if args is not None and not isinstance(args, list):
38
- raise ValueError("Arguments must be a list of strings or None.")
85
+ # Initialize the BackgroundScheduler with the application's timezone
86
+ self.__background_scheduler = APSBackgroundScheduler(
87
+ timezone=pytz.timezone(self.__app.config('app.timezone', 'UTC'))
88
+ )
39
89
 
40
- self.__command = signature
41
- self.__args = args or []
42
- return self
90
+ # Initialize the BlockingScheduler with the application's timezone
91
+ self.__blocking_scheduler = APSBlockingScheduler(
92
+ timezone=pytz.timezone(self.__app.config('app.timezone', 'UTC'))
93
+ )
94
+
95
+ # Initialize the AsyncIOScheduler with the application's timezone
96
+ self.__asyncio_scheduler = APSAsyncIOScheduler(
97
+ timezone=pytz.timezone(self.__app.config('app.timezone', 'UTC'))
98
+ )
99
+
100
+ def __getCommands(
101
+ self
102
+ ) -> dict:
103
+ """
104
+ Retrieve available commands from the reactor and return them as a dictionary.
105
+
106
+ This method queries the reactor for all available jobs/commands, extracting their
107
+ signatures and descriptions. The result is a dictionary where each key is the command
108
+ signature and the value is another dictionary containing the command's signature and
109
+ its description.
110
+
111
+ Returns
112
+ -------
113
+ dict
114
+ A dictionary mapping command signatures to their details. Each value is a dictionary
115
+ with 'signature' and 'description' keys.
116
+ """
117
+
118
+ # Initialize the commands dictionary
119
+ commands = {}
120
+
121
+ # Iterate over all jobs provided by the reactor's info method
122
+ for job in self.__reactor.info():
123
+
124
+ # Store each job's signature and description in the commands dictionary
125
+ commands[job['signature']] = {
126
+ 'signature': job['signature'],
127
+ 'description': job.get('description', '')
128
+ }
129
+
130
+ # Return the commands dictionary
131
+ return commands
132
+
133
+ def background(
134
+ self
135
+ ) -> 'Scheduler':
136
+ """
137
+ Set the scheduler type to 'background' for job scheduling.
138
+
139
+ This method configures the scheduler to use the BackgroundScheduler, which runs jobs in the background using threads.
140
+ It updates the internal type property to indicate that subsequent scheduled jobs should be handled by the background scheduler.
141
+
142
+ Returns
143
+ -------
144
+ Scheduler
145
+ Returns the current instance of the Scheduler to allow method chaining.
146
+ """
43
147
 
44
- def background(self) -> 'Scheduler':
148
+ # Set the scheduler type to 'background'
45
149
  self.__type = 'background'
150
+
151
+ # Return self to support method chaining
46
152
  return self
47
153
 
48
- def blocking(self) -> 'Scheduler':
154
+ def blocking(
155
+ self
156
+ ) -> 'Scheduler':
157
+ """
158
+ Set the scheduler type to 'blocking' for job scheduling.
159
+
160
+ This method configures the scheduler to use the BlockingScheduler, which runs jobs in the foreground and blocks the main thread.
161
+ It updates the internal type property so that subsequent scheduled jobs will be handled by the blocking scheduler.
162
+
163
+ Returns
164
+ -------
165
+ Scheduler
166
+ Returns the current instance of the Scheduler to allow method chaining.
167
+ """
168
+
169
+ # Set the scheduler type to 'blocking'
49
170
  self.__type = 'blocking'
171
+
172
+ # Return self to support method chaining
50
173
  return self
51
174
 
52
- def asyncio(self) -> 'Scheduler':
175
+ def asyncio(
176
+ self
177
+ ) -> 'Scheduler':
178
+ """
179
+ Set the scheduler type to 'asyncio' for job scheduling.
180
+
181
+ This method configures the scheduler to use the AsyncIOScheduler, which integrates with
182
+ asyncio event loops for asynchronous job execution. It updates the internal type property
183
+ so that subsequent scheduled jobs will be handled by the asyncio scheduler.
184
+
185
+ Returns
186
+ -------
187
+ Scheduler
188
+ Returns the current instance of the Scheduler to allow method chaining.
189
+ """
190
+
191
+ # Set the scheduler type to 'asyncio'
53
192
  self.__type = 'asyncio'
193
+
194
+ # Return self to support method chaining
195
+ return self
196
+
197
+ def __isAvailable(
198
+ self,
199
+ signature: str
200
+ ) -> bool:
201
+ """
202
+ Check if a command with the given signature is available.
203
+
204
+ This method iterates through the available commands and determines
205
+ whether the provided signature matches any registered command.
206
+
207
+ Parameters
208
+ ----------
209
+ signature : str
210
+ The signature of the command to check for availability.
211
+
212
+ Returns
213
+ -------
214
+ bool
215
+ True if the command with the specified signature exists and is available,
216
+ False otherwise.
217
+ """
218
+
219
+ # Iterate through all available command signatures
220
+ for command in self.__available_commands.keys():
221
+
222
+ # Return True if the signature matches an available command
223
+ if command == signature:
224
+ return True
225
+
226
+ # Return False if the signature is not found among available commands
227
+ return False
228
+
229
+ def __getDescription(
230
+ self,
231
+ signature: str
232
+ ) -> Optional[str]:
233
+ """
234
+ Retrieve the description of a command given its signature.
235
+
236
+ This method looks up the available commands dictionary and returns the description
237
+ associated with the provided command signature. If the signature does not exist,
238
+ it returns None.
239
+
240
+ Parameters
241
+ ----------
242
+ signature : str
243
+ The unique signature identifying the command.
244
+
245
+ Returns
246
+ -------
247
+ Optional[str]
248
+ The description of the command if found; otherwise, None.
249
+ """
250
+
251
+ # Attempt to retrieve the command entry from the available commands dictionary
252
+ command_entry = self.__available_commands.get(signature)
253
+
254
+ # Return the description if the command exists, otherwise return None
255
+ return command_entry['description'] if command_entry else None
256
+
257
+ def __getScheduler(
258
+ self
259
+ ) -> Optional[Union[APSBackgroundScheduler, APSBlockingScheduler, APSAsyncIOScheduler]]:
260
+ """
261
+ Retrieve the appropriate APScheduler instance based on the current scheduler type.
262
+
263
+ This method selects and returns the internal scheduler instance corresponding to the
264
+ type specified by the user (background, blocking, or asyncio). The scheduler type is
265
+ determined by the value of the internal `__type` attribute, which is set using the
266
+ `background()`, `blocking()`, or `asyncio()` methods.
267
+
268
+ Returns
269
+ -------
270
+ Optional[Union[APSBackgroundScheduler, APSBlockingScheduler, APSAsyncIOScheduler]]
271
+ The scheduler instance matching the current type, or None if the type is not set
272
+ or does not match any known scheduler.
273
+ """
274
+
275
+ # Return the BackgroundScheduler if the type is set to 'background'
276
+ if self.__type == 'background':
277
+ return self.__background_scheduler
278
+
279
+ # Return the BlockingScheduler if the type is set to 'blocking'
280
+ elif self.__type == 'blocking':
281
+ return self.__blocking_scheduler
282
+
283
+ # Return the AsyncIOScheduler if the type is set to 'asyncio'
284
+ elif self.__type == 'asyncio':
285
+ return self.__asyncio_scheduler
286
+
287
+ def __reset(
288
+ self
289
+ ) -> None:
290
+ """
291
+ Reset the internal state of the Scheduler instance.
292
+
293
+ This method clears the current command, arguments, purpose, type, trigger,
294
+ start time, and end time attributes, effectively resetting the scheduler's
295
+ configuration to its initial state. This can be useful for preparing the
296
+ scheduler for a new command or job scheduling without retaining any previous
297
+ settings.
298
+
299
+ Returns
300
+ -------
301
+ None
302
+ This method does not return any value. It modifies the internal state of the Scheduler.
303
+ """
304
+
305
+ self.__command = None
306
+ self.__args = None
307
+ self.__purpose = None
308
+ self.__type = None
309
+
310
+ def command(
311
+ self,
312
+ signature: str,
313
+ args: Optional[List[str]] = None
314
+ ) -> 'Scheduler':
315
+ """
316
+ Register a command to be scheduled with the specified signature and optional arguments.
317
+
318
+ This method validates the provided command signature and arguments, checks if the command
319
+ is available in the list of registered commands, and stores the command details internally
320
+ for scheduling. The command's description is also retrieved and stored for reference.
321
+
322
+ Parameters
323
+ ----------
324
+ signature : str
325
+ The unique signature identifying the command to be scheduled. Must be a non-empty string.
326
+ args : Optional[List[str]], optional
327
+ A list of string arguments to be passed to the command. If not provided, an empty list is used.
328
+
329
+ Returns
330
+ -------
331
+ Scheduler
332
+ Returns the current instance of the Scheduler to allow method chaining.
333
+
334
+ Raises
335
+ ------
336
+ ValueError
337
+ If the signature is not a non-empty string, if the arguments are not a list or None,
338
+ or if the command signature is not available among registered commands.
339
+ """
340
+
341
+ # Validate that the command signature is a non-empty string
342
+ if not isinstance(signature, str) or not signature.strip():
343
+ raise ValueError("Command signature must be a non-empty string.")
344
+
345
+ # Ensure that arguments are either a list of strings or None
346
+ if args is not None and not isinstance(args, list):
347
+ raise ValueError("Arguments must be a list of strings or None.")
348
+
349
+ # Check if the command is available in the registered commands
350
+ if not self.__isAvailable(signature):
351
+ raise ValueError(f"The command '{signature}' is not available or does not exist.")
352
+
353
+ # Store the command signature
354
+ self.__command = signature
355
+
356
+ # If purpose is not already set, retrieve and set the command's description
357
+ if self.__purpose is None:
358
+ self.__purpose = self.__getDescription(signature)
359
+
360
+ # Store the provided arguments or default to an empty list
361
+ self.__args = args if args is not None else []
362
+
363
+ # Return self to support method chaining
54
364
  return self
55
365
 
56
- def porpose(self, purpose: str) -> 'Scheduler':
366
+ def purpose(
367
+ self,
368
+ purpose: str
369
+ ) -> 'Scheduler':
370
+ """
371
+ Set the purpose or description for the scheduled command.
372
+
373
+ This method assigns a human-readable purpose or description to the command
374
+ that is being scheduled. The purpose must be a non-empty string. This can
375
+ be useful for documentation, logging, or displaying information about the
376
+ scheduled job.
377
+
378
+ Parameters
379
+ ----------
380
+ purpose : str
381
+ The purpose or description to associate with the scheduled command.
382
+ Must be a non-empty string.
383
+
384
+ Returns
385
+ -------
386
+ Scheduler
387
+ Returns the current instance of the Scheduler to allow method chaining.
388
+
389
+ Raises
390
+ ------
391
+ ValueError
392
+ If the provided purpose is not a non-empty string.
393
+ """
394
+
395
+ # Validate that the purpose is a non-empty string
57
396
  if not isinstance(purpose, str) or not purpose.strip():
58
397
  raise ValueError("The purpose must be a non-empty string.")
398
+
399
+ # Set the internal purpose attribute
59
400
  self.__purpose = purpose
401
+
402
+ # Return self to support method chaining
60
403
  return self
61
404
 
62
- def onceAt(self, date: datetime) -> 'Scheduler':
405
+ def onceAt(
406
+ self,
407
+ date: datetime
408
+ ) -> bool:
409
+ """
410
+ Schedule a command to run once at a specific date and time.
411
+
412
+ This method schedules the currently registered command to execute exactly once at the
413
+ specified datetime. If no scheduler type has been set, it defaults to using the background
414
+ scheduler. The job is registered internally and added to the appropriate APScheduler instance.
415
+
416
+ Parameters
417
+ ----------
418
+ date : datetime.datetime
419
+ The date and time at which the command should be executed. Must be a valid `datetime` instance.
420
+
421
+ Returns
422
+ -------
423
+ bool
424
+ Returns True if the job was successfully scheduled.
425
+
426
+ Raises
427
+ ------
428
+ CLIOrionisRuntimeError
429
+ If the provided date is not a `datetime` instance, if the scheduler type is not defined,
430
+ or if there is an error while scheduling the job.
431
+ """
432
+
433
+ try:
434
+
435
+ # Ensure the provided date is a valid datetime instance.
436
+ if not isinstance(date, datetime):
437
+ raise CLIOrionisRuntimeError(
438
+ "The date must be an instance of datetime."
439
+ )
440
+
441
+ # If no scheduler type is set, default to background scheduler.
442
+ if self.__type is None:
443
+ self.background()
444
+
445
+ # Register the job details internally.
446
+ self.__jobs[self.__command] = {
447
+ 'signature': self.__command,
448
+ 'args': self.__args,
449
+ 'purpose': self.__purpose,
450
+ 'type': self.__type,
451
+ 'trigger': 'once_at',
452
+ 'start_at': date.strftime('%Y-%m-%d %H:%M:%S'),
453
+ 'end_at': date.strftime('%Y-%m-%d %H:%M:%S')
454
+ }
455
+
456
+ # Retrieve the appropriate scheduler instance.
457
+ scheduler = self.__getScheduler()
458
+
459
+ # Raise an error if the scheduler is not defined.
460
+ if scheduler is None:
461
+ raise CLIOrionisRuntimeError("No scheduler type has been defined.")
462
+
463
+ # Add the job to the scheduler.
464
+ scheduler.add_job(
465
+ func= lambda command=self.__command, args=list(self.__args): self.__reactor.call(
466
+ command,
467
+ args
468
+ ),
469
+ trigger=DateTrigger(
470
+ run_date=date
471
+ ),
472
+ id=self.__command,
473
+ name=self.__command,
474
+ replace_existing=True
475
+ )
476
+
477
+ # Reset the internal state for future scheduling.
478
+ self.__reset()
479
+
480
+ # Return True to indicate successful scheduling.
481
+ return True
482
+
483
+ except Exception as e:
484
+
485
+ # Reraise known CLIOrionisRuntimeError exceptions.
486
+ if isinstance(e, CLIOrionisRuntimeError):
487
+ raise e
488
+
489
+ # Wrap and raise any other exceptions as CLIOrionisRuntimeError.
490
+ raise CLIOrionisRuntimeError(f"Error scheduling the job: {str(e)}")
491
+
492
+ def start(self) -> None:
493
+ """
494
+ Start all internal APScheduler instances (AsyncIO, Background, and Blocking).
63
495
 
64
- if not isinstance(date, datetime):
65
- raise CLIOrionisRuntimeError("The date must be an instance of datetime.")
496
+ This method initiates the three scheduler types managed by this Scheduler instance:
497
+ - AsyncIOScheduler: Integrates with asyncio event loops for asynchronous job execution.
498
+ - BackgroundScheduler: Runs jobs in the background using threads.
499
+ - BlockingScheduler: Runs jobs in the foreground and blocks the main thread.
66
500
 
67
- if self.__command is None:
68
- raise CLIOrionisRuntimeError("You must define a command before scheduling it.")
501
+ Each scheduler is started, allowing scheduled jobs to be executed according to their triggers.
69
502
 
70
- if self.__type is None:
71
- self.background()
503
+ Returns
504
+ -------
505
+ None
506
+ This method does not return any value. It starts all configured schedulers.
507
+ """
72
508
 
73
- if self.__purpose is None:
74
- self.__purpose = "Scheduled task"
509
+ # Start the AsyncIOScheduler to handle asynchronous jobs.
510
+ # Only start if there's an event loop running or we can create one
511
+ try:
512
+ asyncio.get_running_loop()
513
+ self.__asyncio_scheduler.start()
514
+ except RuntimeError:
515
+ # No event loop is running, AsyncIOScheduler won't be started
516
+ # This is normal for non-asyncio environments
517
+ pass
75
518
 
76
- return self
519
+ # Start the BackgroundScheduler to handle background jobs.
520
+ self.__background_scheduler.start()
521
+
522
+ # Start the BlockingScheduler to handle blocking jobs.
523
+ self.__blocking_scheduler.start()
524
+
525
+ def shutdown(self, wait=True) -> None:
526
+ """
527
+ Shut down all internal APScheduler instances (AsyncIO, Background, and Blocking).
528
+
529
+ This method gracefully stops the three scheduler types managed by this Scheduler instance:
530
+ - AsyncIOScheduler: Handles asynchronous job execution.
531
+ - BackgroundScheduler: Runs jobs in the background using threads.
532
+ - BlockingScheduler: Runs jobs in the foreground and blocks the main thread.
533
+
534
+ Parameters
535
+ ----------
536
+ wait : bool, optional
537
+ If True, the method will wait until all currently executing jobs are completed before shutting down the schedulers.
538
+ If False, the schedulers will be shut down immediately without waiting for running jobs to finish. Default is True.
539
+
540
+ Returns
541
+ -------
542
+ None
543
+ This method does not return any value. It shuts down all configured schedulers.
544
+ """
77
545
 
546
+ # Validate that the wait parameter is a boolean.
547
+ if not isinstance(wait, bool):
548
+ raise ValueError("The 'wait' parameter must be a boolean value.")
78
549
 
550
+ # Shut down the AsyncIOScheduler, waiting for jobs if specified.
551
+ try:
552
+ if self.__asyncio_scheduler.running:
553
+ self.__asyncio_scheduler.shutdown(wait=wait)
554
+ except Exception:
555
+ # AsyncIOScheduler may not be running or may have issues in shutdown
556
+ pass
79
557
 
558
+ # Shut down the BackgroundScheduler, waiting for jobs if specified.
559
+ if self.__background_scheduler.running:
560
+ self.__background_scheduler.shutdown(wait=wait)
80
561
 
562
+ # Shut down the BlockingScheduler, waiting for jobs if specified.
563
+ if self.__blocking_scheduler.running:
564
+ self.__blocking_scheduler.shutdown(wait=wait)
81
565
 
82
- # class Scheduler2():
83
-
84
- # def __init__(
85
- # self,
86
- # app: IApplication,
87
- # reactor: IReactor
88
- # ) -> None:
89
- # self.__app = app or Orionis()
90
- # self.__jobs: dict = {}
91
- # self.__command: str = None
92
- # self.__args: List[str] = None
93
- # self.__purpose: str = None
94
- # self.__trigger: Optional[CronTrigger | DateTrigger | IntervalTrigger] = None
95
- # self.__scheduler: Optional[APSBackgroundScheduler | APSBlockingScheduler | APSAsyncIOScheduler] = None
96
- # self.__reactor = reactor
97
- # self.__available_commands = self.__reactor.info()
98
-
99
- # def background(self):
100
- # self.__scheduler = APSBackgroundScheduler(
101
- # timezone=self.__app.config('app.timezone', 'UTC')
102
- # )
103
- # return self
104
-
105
- # def blocking(self):
106
- # self.__scheduler = APSBlockingScheduler(
107
- # timezone=self.__app.config('app.timezone', 'UTC')
108
- # )
109
- # return self
566
+ def remove(self, signature:str) -> None:
567
+ """
568
+ Remove a scheduled job from all internal APScheduler instances.
569
+
570
+ This method attempts to remove a job with the specified job ID from each of the
571
+ managed schedulers: AsyncIOScheduler, BackgroundScheduler, and BlockingScheduler.
572
+ If the job does not exist in a particular scheduler, that scheduler will ignore
573
+ the removal request without raising an error.
574
+
575
+ Parameters
576
+ ----------
577
+ signature : str
578
+ The unique identifier of the job to be removed from the schedulers.
579
+
580
+ Returns
581
+ -------
582
+ None
583
+ This method does not return any value. It removes the job from all schedulers if present.
584
+ """
110
585
 
111
- # def asyncio(self):
112
- # self.__scheduler = APSAsyncIOScheduler(
113
- # timezone=self.__app.config('app.timezone', 'UTC')
114
- # )
115
- # return self
586
+ # Validate that the job signature is a non-empty string.
587
+ if not isinstance(signature, str) or not signature.strip():
588
+ raise ValueError("Job signature must be a non-empty string.")
589
+
590
+ # Remove the job from the AsyncIOScheduler, if it exists.
591
+ try:
592
+ self.__asyncio_scheduler.remove_job(signature)
593
+ except Exception:
594
+ # Job may not exist in this scheduler, continue with others
595
+ pass
596
+
597
+ # Remove the job from the BackgroundScheduler, if it exists.
598
+ try:
599
+ self.__background_scheduler.remove_job(signature)
600
+ except Exception:
601
+ # Job may not exist in this scheduler, continue with others
602
+ pass
603
+
604
+ # Remove the job from the BlockingScheduler, if it exists.
605
+ try:
606
+ self.__blocking_scheduler.remove_job(signature)
607
+ except Exception:
608
+ # Job may not exist in this scheduler, ignore
609
+ pass
610
+
611
+ def jobs(self) -> dict:
612
+ """
613
+ Retrieve all scheduled jobs currently managed by the Scheduler.
614
+
615
+ This method returns a dictionary containing information about all jobs that have been
616
+ registered and scheduled through this Scheduler instance. Each entry in the dictionary
617
+ represents a scheduled job, where the key is the command signature and the value is a
618
+ dictionary with details such as the signature, arguments, purpose, type, trigger, start time,
619
+ and end time.
620
+
621
+ Returns
622
+ -------
623
+ dict
624
+ A dictionary mapping command signatures to their corresponding job details. Each value
625
+ is a dictionary containing information about the scheduled job.
626
+ """
116
627
 
117
- # def command(self, signature: str, args: Optional[List[str]] = None) -> 'Scheduler':
118
- # """
119
- # Set the command signature and its arguments.
120
- # """
121
- # if not isinstance(signature, str) or not signature.strip():
122
- # raise ValueError("The command signature must be a non-empty string.")
628
+ # Return the internal dictionary holding all scheduled jobs and their details.
629
+ return self.__jobs
123
630
 
124
- # if args is not None and not isinstance(args, list):
125
- # raise ValueError("Arguments must be a list of strings or None.")
631
+ def start_asyncio_scheduler(self) -> bool:
632
+ """
633
+ Start the AsyncIOScheduler specifically.
126
634
 
127
- # self.__command = signature
128
- # self.__args = args or []
129
- # return self
635
+ This method attempts to start only the AsyncIOScheduler. It's useful when you need
636
+ to start the asyncio scheduler separately or when working in an asyncio environment.
130
637
 
131
- # def __isAvailable(self, signature: str) -> bool:
132
- # for command in self.__available_commands:
133
- # if command['signature'] == signature:
134
- # return True
135
- # return False
638
+ Returns
639
+ -------
640
+ bool
641
+ True if the AsyncIOScheduler was started successfully, False otherwise.
642
+ """
136
643
 
137
- # def __getDescription(self, signature: str) -> Optional[str]:
138
- # """
139
- # Get the description of the command by its signature.
140
- # """
141
- # for command in self.__available_commands:
142
- # if command['signature'] == signature:
143
- # return command.get('description')
144
- # return None
644
+ try:
645
+ # Check if we're in an asyncio environment
646
+ asyncio.get_running_loop()
647
+ if not self.__asyncio_scheduler.running:
648
+ self.__asyncio_scheduler.start()
649
+ return True
650
+ except RuntimeError:
651
+ # No event loop is running
652
+ return False
653
+ except Exception:
654
+ # Other errors
655
+ return False
656
+
657
+ async def start_asyncio_scheduler_async(self) -> bool:
658
+ """
659
+ Start the AsyncIOScheduler in an async context.
145
660
 
146
- # def command(
147
- # self,
148
- # signature: str,
149
- # args: Optional[List[str]] = None
150
- # ) -> bool:
661
+ This method is designed to be called from async functions and ensures
662
+ the AsyncIOScheduler is properly started within an asyncio event loop.
151
663
 
152
- # # Validar que la firma del comando sea una cadena no vacía
153
- # if not isinstance(signature, str) or not signature.strip():
154
- # raise ValueError("La firma del comando debe ser una cadena no vacía.")
664
+ Returns
665
+ -------
666
+ bool
667
+ True if the AsyncIOScheduler was started successfully, False otherwise.
668
+ """
155
669
 
156
- # # Garantizar que los argumentos sean una lista de cadenas o None
157
- # if args is not None and not isinstance(args, list):
158
- # raise ValueError("Los argumentos deben ser una lista de cadenas o None.")
670
+ try:
671
+ if not self.__asyncio_scheduler.running:
672
+ self.__asyncio_scheduler.start()
673
+ return True
674
+ except Exception:
675
+ return False
159
676
 
160
- # # Verificar si el comando ya está registrado
161
- # if not self.__isAvailable(signature):
162
- # raise ValueError(f"El comando '{signature}' no está disponible o no existe.")
677
+ def is_asyncio_scheduler_running(self) -> bool:
678
+ """
679
+ Check if the AsyncIOScheduler is currently running.
163
680
 
164
- # # Almacenar el trabajo en el diccionario de trabajos
165
- # self.__jobs[signature] = {
166
- # "signature": signature,
167
- # "args": args or [],
168
-
169
- # }
681
+ Returns
682
+ -------
683
+ bool
684
+ True if the AsyncIOScheduler is running, False otherwise.
685
+ """
170
686
 
171
- # # Retornar la misma instancia para permitir encadenamiento
172
- # return self
687
+ return self.__asyncio_scheduler.running if self.__asyncio_scheduler else False
173
688
 
174
- # def onceAt(self, date: datetime):
175
- # """
176
- # Schedule the defined command to execute every X seconds.
177
- # """
178
-
179
- # if not isinstance(date, datetime):
180
- # raise CLIOrionisRuntimeError(
181
- # "La fecha debe ser una instancia de datetime."
182
- # )
183
-
184
- # self.__scheduler.add_job(
185
- # func=lambda: self.__reactor.run(self.__jobs['signature'], *self.__jobs['args']),
186
- # trigger=DateTrigger(run_date=date, timezone=self.__timezone),
187
- # args=[self.__jobs],
188
- # id=self.__jobs['signature'],
189
- # replace_existing=True
190
- # )
191
-
192
-
193
-
194
-
195
- # # def start(self):
196
- # # self.__scheduler.start()
197
-
198
- # # def shutdown(self, wait=True):
199
- # # self.__scheduler.shutdown(wait=wait)
200
-
201
- # # def remove(self, job_id):
202
- # # self.__scheduler.remove_job(job_id)
203
-
204
- # # def jobs(self):
205
- # # return self.__scheduler.get_jobs()
689
+ def get_scheduler_status(self) -> dict:
690
+ """
691
+ Get the status of all schedulers.
206
692
 
693
+ Returns
694
+ -------
695
+ dict
696
+ A dictionary with the running status of each scheduler type.
697
+ """
207
698
 
699
+ return {
700
+ 'asyncio': self.__asyncio_scheduler.running if self.__asyncio_scheduler else False,
701
+ 'background': self.__background_scheduler.running if self.__background_scheduler else False,
702
+ 'blocking': self.__blocking_scheduler.running if self.__blocking_scheduler else False
703
+ }
@@ -103,6 +103,7 @@ class App(BaseEntity):
103
103
  )
104
104
 
105
105
  timezone: str = field(
106
+ # default_factory = lambda: Env.get('APP_TIMEZONE', 'UTC'),
106
107
  default_factory = lambda: Env.get('APP_TIMEZONE', 'UTC'),
107
108
  metadata = {
108
109
  "description": "The timezone of the application. Defaults to 'UTC'.",
@@ -5,7 +5,7 @@
5
5
  NAME = "orionis"
6
6
 
7
7
  # Current version of the framework
8
- VERSION = "0.469.0"
8
+ VERSION = "0.470.0"
9
9
 
10
10
  # Full name of the author or maintainer of the project
11
11
  AUTHOR = "Raul Mauricio Uñate Castro"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: orionis
3
- Version: 0.469.0
3
+ Version: 0.470.0
4
4
  Summary: Orionis Framework – Elegant, Fast, and Powerful.
5
5
  Home-page: https://github.com/orionis-framework/framework
6
6
  Author: Raul Mauricio Uñate Castro
@@ -45,7 +45,7 @@ orionis/console/output/contracts/executor.py,sha256=7l3kwnvv6GlH9EYk0v94YE1olex_
45
45
  orionis/console/output/enums/__init__.py,sha256=LAaAxg-DpArCjf_jqZ0_9s3p8899gntDYkSU_ppTdC8,66
46
46
  orionis/console/output/enums/styles.py,sha256=6a4oQCOBOKMh2ARdeq5GlIskJ3wjiylYmh66tUKKmpQ,4053
47
47
  orionis/console/tasks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
48
- orionis/console/tasks/schedule.py,sha256=3gnXPG2lamqRW99ERJE6c3WVuek6jiFqOqyiT_cbkWg,7213
48
+ orionis/console/tasks/schedule.py,sha256=ANRuPRRd-znbMD5fcPNgKAv69XOECWod7mugF5_dt0U,26636
49
49
  orionis/container/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
50
50
  orionis/container/container.py,sha256=MmvFm0Y-x667mIYPunmBnHzQHBsWJObT5_zuWrgqaWU,80528
51
51
  orionis/container/context/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -83,7 +83,7 @@ orionis/foundation/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJW
83
83
  orionis/foundation/config/startup.py,sha256=vbzduprRCNyYeR2nnMaqc1uKXw6PTzAY2jVfXNQKN8I,9691
84
84
  orionis/foundation/config/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
85
85
  orionis/foundation/config/app/entities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
86
- orionis/foundation/config/app/entities/app.py,sha256=TVom0M_wRpw2Jqan8E_JgQAruSifRyRhVh8xD7GJmvI,10202
86
+ orionis/foundation/config/app/entities/app.py,sha256=K2dbHhxRNtp454-M5pGBzSpLOBu8tLaDv_iwxuwbR3g,10271
87
87
  orionis/foundation/config/app/enums/__init__.py,sha256=L0csn3OmlPT4Y4WhTSI2uQRua0Z367MxNs12AcSHsV4,120
88
88
  orionis/foundation/config/app/enums/ciphers.py,sha256=N9qdLw47ug9FslHFckP8Sz7MfDN74zujZLh37mtLzW8,1249
89
89
  orionis/foundation/config/app/enums/environments.py,sha256=W81oUasW__nRDiyhScMfaBpQ65RXkAAvRGd4ShDFTvE,409
@@ -190,7 +190,7 @@ orionis/foundation/providers/reactor_provider.py,sha256=P0KQcp4AFKTrD6BStGfCTqhG
190
190
  orionis/foundation/providers/testing_provider.py,sha256=SrJRpdvcblx9WvX7x9Y3zc7OQfiTf7la0HAJrm2ESlE,3725
191
191
  orionis/foundation/providers/workers_provider.py,sha256=oa_2NIDH6UxZrtuGkkoo_zEoNIMGgJ46vg5CCgAm7wI,3926
192
192
  orionis/metadata/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
193
- orionis/metadata/framework.py,sha256=Qcg83tzpMW43N_S0LaF3rqKgjbAC1YWGdstbZ8WWsNA,4109
193
+ orionis/metadata/framework.py,sha256=GN5YICH6x7kPf0sNqbd0Rcqz3V2YozrT9UqvC6NtpzQ,4109
194
194
  orionis/metadata/package.py,sha256=k7Yriyp5aUcR-iR8SK2ec_lf0_Cyc-C7JczgXa-I67w,16039
195
195
  orionis/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
196
196
  orionis/services/asynchrony/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -356,7 +356,7 @@ orionis/test/validators/web_report.py,sha256=n9BfzOZz6aEiNTypXcwuWbFRG0OdHNSmCNu
356
356
  orionis/test/validators/workers.py,sha256=rWcdRexINNEmGaO7mnc1MKUxkHKxrTsVuHgbnIfJYgc,1206
357
357
  orionis/test/view/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
358
358
  orionis/test/view/render.py,sha256=f-zNhtKSg9R5Njqujbg2l2amAs2-mRVESneLIkWOZjU,4082
359
- orionis-0.469.0.dist-info/licenses/LICENCE,sha256=JhC-z_9mbpUrCfPjcl3DhDA8trNDMzb57cvRSam1avc,1463
359
+ orionis-0.470.0.dist-info/licenses/LICENCE,sha256=JhC-z_9mbpUrCfPjcl3DhDA8trNDMzb57cvRSam1avc,1463
360
360
  tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
361
361
  tests/container/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
362
362
  tests/container/context/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -499,8 +499,8 @@ tests/testing/validators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZ
499
499
  tests/testing/validators/test_testing_validators.py,sha256=WPo5GxTP6xE-Dw3X1vZoqOMpb6HhokjNSbgDsDRDvy4,16588
500
500
  tests/testing/view/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
501
501
  tests/testing/view/test_render.py,sha256=tnnMBwS0iKUIbogLvu-7Rii50G6Koddp3XT4wgdFEYM,1050
502
- orionis-0.469.0.dist-info/METADATA,sha256=d7pqVfFyMWdL-FVK8XDWrHslTXmTkttTsZ52vcognFo,4801
503
- orionis-0.469.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
504
- orionis-0.469.0.dist-info/top_level.txt,sha256=2bdoHgyGZhOtLAXS6Om8OCTmL24dUMC_L1quMe_ETbk,14
505
- orionis-0.469.0.dist-info/zip-safe,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
506
- orionis-0.469.0.dist-info/RECORD,,
502
+ orionis-0.470.0.dist-info/METADATA,sha256=EoWf7FtN4Tx2TZUaF82iMV6_fJSz5qV4PKPmvEc6tH4,4801
503
+ orionis-0.470.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
504
+ orionis-0.470.0.dist-info/top_level.txt,sha256=2bdoHgyGZhOtLAXS6Om8OCTmL24dUMC_L1quMe_ETbk,14
505
+ orionis-0.470.0.dist-info/zip-safe,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
506
+ orionis-0.470.0.dist-info/RECORD,,