orionis 0.472.0__py3-none-any.whl → 0.474.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.
@@ -520,7 +520,7 @@ class Reactor(IReactor):
520
520
  command_instance._args = {}
521
521
 
522
522
  # Call the handle method of the command instance
523
- output = self.__app.call(command_instance, 'handle')
523
+ output = self.__app.call(command_instance, 'handle')
524
524
 
525
525
  # Log the command execution completion with DONE state
526
526
  elapsed_time = round(time.perf_counter() - start_time, 2)
@@ -1,6 +1,4 @@
1
1
  from typing import Any, List, Optional
2
- from apscheduler.schedulers.background import BackgroundScheduler as APSBackgroundScheduler
3
- from apscheduler.schedulers.blocking import BlockingScheduler as APSBlockingScheduler
4
2
  from apscheduler.schedulers.asyncio import AsyncIOScheduler as APSAsyncIOScheduler
5
3
  from apscheduler.triggers.cron import CronTrigger
6
4
  from apscheduler.triggers.date import DateTrigger
@@ -9,7 +7,6 @@ from orionis.console.contracts.reactor import IReactor
9
7
  from datetime import datetime
10
8
  import pytz
11
9
  import asyncio
12
- from typing import Union
13
10
  from orionis.console.exceptions import CLIOrionisRuntimeError
14
11
  from orionis.app import Orionis
15
12
 
@@ -23,7 +20,7 @@ class Scheduler():
23
20
  Initialize a new instance of the Scheduler class.
24
21
 
25
22
  This constructor sets up the internal state required for scheduling commands,
26
- including references to the application instance, APScheduler schedulers, the
23
+ including references to the application instance, AsyncIOScheduler, the
27
24
  command reactor, and job tracking structures. It also initializes properties
28
25
  for managing the current scheduling context.
29
26
 
@@ -42,11 +39,10 @@ class Scheduler():
42
39
  # Store the application instance for configuration access.
43
40
  self.__app = Orionis()
44
41
 
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()
42
+ # Initialize AsyncIOScheduler instance with timezone configuration.
43
+ self.__scheduler: APSAsyncIOScheduler = APSAsyncIOScheduler(
44
+ timezone=pytz.timezone(self.__app.config('app.timezone', 'UTC'))
45
+ )
50
46
 
51
47
  # Store the reactor instance for command management.
52
48
  self.__reactor = reactor
@@ -61,41 +57,6 @@ class Scheduler():
61
57
  self.__command: str = None # The command signature to be scheduled.
62
58
  self.__args: List[str] = None # Arguments for the command.
63
59
  self.__purpose: str = None # Purpose or description of the scheduled job.
64
- self.__type: str = None # Scheduler type (background, blocking, asyncio).
65
-
66
- def __initScheduler(
67
- self
68
- ) -> None:
69
- """
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.
83
- """
84
-
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
- )
89
-
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
60
 
100
61
  def __getCommands(
101
62
  self
@@ -130,70 +91,6 @@ class Scheduler():
130
91
  # Return the commands dictionary
131
92
  return commands
132
93
 
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
- """
147
-
148
- # Set the scheduler type to 'background'
149
- self.__type = 'background'
150
-
151
- # Return self to support method chaining
152
- return self
153
-
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'
170
- self.__type = 'blocking'
171
-
172
- # Return self to support method chaining
173
- return self
174
-
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'
192
- self.__type = 'asyncio'
193
-
194
- # Return self to support method chaining
195
- return self
196
-
197
94
  def __isAvailable(
198
95
  self,
199
96
  signature: str
@@ -254,47 +151,16 @@ class Scheduler():
254
151
  # Return the description if the command exists, otherwise return None
255
152
  return command_entry['description'] if command_entry else None
256
153
 
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
154
  def __reset(
288
155
  self
289
156
  ) -> None:
290
157
  """
291
158
  Reset the internal state of the Scheduler instance.
292
159
 
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.
160
+ This method clears the current command, arguments, and purpose attributes,
161
+ effectively resetting the scheduler's configuration to its initial state.
162
+ This can be useful for preparing the scheduler for a new command or job
163
+ scheduling without retaining any previous settings.
298
164
 
299
165
  Returns
300
166
  -------
@@ -305,7 +171,6 @@ class Scheduler():
305
171
  self.__command = None
306
172
  self.__args = None
307
173
  self.__purpose = None
308
- self.__type = None
309
174
 
310
175
  def command(
311
176
  self,
@@ -410,8 +275,8 @@ class Scheduler():
410
275
  Schedule a command to run once at a specific date and time.
411
276
 
412
277
  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.
278
+ specified datetime using the AsyncIOScheduler. The job is registered internally and
279
+ added to the scheduler instance.
415
280
 
416
281
  Parameters
417
282
  ----------
@@ -426,8 +291,7 @@ class Scheduler():
426
291
  Raises
427
292
  ------
428
293
  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.
294
+ If the provided date is not a `datetime` instance or if there is an error while scheduling the job.
431
295
  """
432
296
 
433
297
  try:
@@ -438,30 +302,18 @@ class Scheduler():
438
302
  "The date must be an instance of datetime."
439
303
  )
440
304
 
441
- # If no scheduler type is set, default to background scheduler.
442
- if self.__type is None:
443
- self.background()
444
-
445
305
  # Register the job details internally.
446
306
  self.__jobs[self.__command] = {
447
307
  'signature': self.__command,
448
308
  'args': self.__args,
449
309
  'purpose': self.__purpose,
450
- 'type': self.__type,
451
310
  'trigger': 'once_at',
452
311
  'start_at': date.strftime('%Y-%m-%d %H:%M:%S'),
453
312
  'end_at': date.strftime('%Y-%m-%d %H:%M:%S')
454
313
  }
455
314
 
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
315
  # Add the job to the scheduler.
464
- scheduler.add_job(
316
+ self.__scheduler.add_job(
465
317
  func= lambda command=self.__command, args=list(self.__args): self.__reactor.call(
466
318
  command,
467
319
  args
@@ -489,124 +341,135 @@ class Scheduler():
489
341
  # Wrap and raise any other exceptions as CLIOrionisRuntimeError.
490
342
  raise CLIOrionisRuntimeError(f"Error scheduling the job: {str(e)}")
491
343
 
492
- def start(self) -> None:
344
+ async def start(self) -> None:
493
345
  """
494
- Start all internal APScheduler instances (AsyncIO, Background, and Blocking).
495
-
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.
346
+ Start the AsyncIO scheduler instance and keep it running.
500
347
 
501
- Each scheduler is started, allowing scheduled jobs to be executed according to their triggers.
348
+ This method initiates the AsyncIOScheduler which integrates with asyncio event loops
349
+ for asynchronous job execution. It ensures the scheduler starts properly within
350
+ an asyncio context and maintains the event loop active to process scheduled jobs.
502
351
 
503
352
  Returns
504
353
  -------
505
354
  None
506
- This method does not return any value. It starts all configured schedulers.
355
+ This method does not return any value. It starts the AsyncIO scheduler and keeps it running.
507
356
  """
508
357
 
509
358
  # Start the AsyncIOScheduler to handle asynchronous jobs.
510
- # Only start if there's an event loop running or we can create one
511
359
  try:
360
+
361
+ # Ensure we're in an asyncio context
512
362
  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
518
363
 
519
- # Start the BackgroundScheduler to handle background jobs.
520
- self.__background_scheduler.start()
364
+ # Start the scheduler
365
+ if not self.__scheduler.running:
366
+ self.__scheduler.start()
367
+
368
+ # Keep the event loop alive to process scheduled jobs
369
+ try:
370
+
371
+ # Wait for the scheduler to start and keep it running
372
+ while True:
373
+ await asyncio.sleep(1)
374
+
375
+ except KeyboardInterrupt:
376
+
377
+ # Handle graceful shutdown on keyboard interrupt
378
+ await self.shutdown()
379
+
380
+ except Exception as e:
521
381
 
522
- # Start the BlockingScheduler to handle blocking jobs.
523
- self.__blocking_scheduler.start()
382
+ # Handle exceptions that may occur during scheduler startup
383
+ raise CLIOrionisRuntimeError(f"Failed to start the scheduler: {str(e)}")
524
384
 
525
- def shutdown(self, wait=True) -> None:
385
+ async def shutdown(self, wait=True) -> None:
526
386
  """
527
- Shut down all internal APScheduler instances (AsyncIO, Background, and Blocking).
387
+ Shut down the AsyncIO scheduler instance asynchronously.
528
388
 
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.
389
+ This method gracefully stops the AsyncIOScheduler that handles asynchronous job execution.
390
+ Using async ensures proper cleanup in asyncio environments.
533
391
 
534
392
  Parameters
535
393
  ----------
536
394
  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.
395
+ If True, the method will wait until all currently executing jobs are completed before shutting down the scheduler.
396
+ If False, the scheduler will be shut down immediately without waiting for running jobs to finish. Default is True.
539
397
 
540
398
  Returns
541
399
  -------
542
400
  None
543
- This method does not return any value. It shuts down all configured schedulers.
401
+ This method does not return any value. It shuts down the AsyncIO scheduler.
544
402
  """
545
403
 
546
404
  # Validate that the wait parameter is a boolean.
547
405
  if not isinstance(wait, bool):
548
406
  raise ValueError("The 'wait' parameter must be a boolean value.")
549
407
 
550
- # Shut down the AsyncIOScheduler, waiting for jobs if specified.
551
408
  try:
552
- if self.__asyncio_scheduler.running:
553
- self.__asyncio_scheduler.shutdown(wait=wait)
409
+
410
+ # Shut down the AsyncIOScheduler, waiting for jobs if specified.
411
+ if self.__scheduler.running:
412
+
413
+ # For AsyncIOScheduler, shutdown can be called normally
414
+ # but we await any pending operations
415
+ self.__scheduler.shutdown(wait=wait)
416
+
417
+ # Give a small delay to ensure proper cleanup
418
+ if wait:
419
+ await asyncio.sleep(0.1)
420
+
554
421
  except Exception:
422
+
555
423
  # AsyncIOScheduler may not be running or may have issues in shutdown
556
424
  pass
557
425
 
558
- # Shut down the BackgroundScheduler, waiting for jobs if specified.
559
- if self.__background_scheduler.running:
560
- self.__background_scheduler.shutdown(wait=wait)
561
-
562
- # Shut down the BlockingScheduler, waiting for jobs if specified.
563
- if self.__blocking_scheduler.running:
564
- self.__blocking_scheduler.shutdown(wait=wait)
565
-
566
- def remove(self, signature:str) -> None:
426
+ async def remove(self, signature: str) -> bool:
567
427
  """
568
- Remove a scheduled job from all internal APScheduler instances.
428
+ Remove a scheduled job from the AsyncIO scheduler asynchronously.
569
429
 
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.
430
+ This method removes a job with the specified signature from both the internal
431
+ jobs dictionary and the AsyncIOScheduler instance. Using async ensures proper
432
+ cleanup in asyncio environments.
574
433
 
575
434
  Parameters
576
435
  ----------
577
436
  signature : str
578
- The unique identifier of the job to be removed from the schedulers.
437
+ The signature of the command/job to remove from the scheduler.
579
438
 
580
439
  Returns
581
440
  -------
582
- None
583
- This method does not return any value. It removes the job from all schedulers if present.
441
+ bool
442
+ Returns True if the job was successfully removed, False if the job was not found.
443
+
444
+ Raises
445
+ ------
446
+ ValueError
447
+ If the signature is not a non-empty string.
584
448
  """
585
449
 
586
- # Validate that the job signature is a non-empty string.
450
+ # Validate that the signature is a non-empty string
587
451
  if not isinstance(signature, str) or not signature.strip():
588
- raise ValueError("Job signature must be a non-empty string.")
452
+ raise ValueError("Signature must be a non-empty string.")
589
453
 
590
- # Remove the job from the AsyncIOScheduler, if it exists.
591
454
  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
455
 
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
456
+ # Remove from the scheduler
457
+ self.__scheduler.remove_job(signature)
458
+
459
+ # Remove from internal jobs dictionary
460
+ if signature in self.__jobs:
461
+ del self.__jobs[signature]
462
+
463
+ # Give a small delay to ensure proper cleanup
464
+ await asyncio.sleep(0.01)
465
+
466
+ # Return True to indicate successful removal
467
+ return True
603
468
 
604
- # Remove the job from the BlockingScheduler, if it exists.
605
- try:
606
- self.__blocking_scheduler.remove_job(signature)
607
469
  except Exception:
608
- # Job may not exist in this scheduler, ignore
609
- pass
470
+
471
+ # Job not found or other error
472
+ return False
610
473
 
611
474
  def jobs(self) -> dict:
612
475
  """
@@ -626,78 +489,4 @@ class Scheduler():
626
489
  """
627
490
 
628
491
  # Return the internal dictionary holding all scheduled jobs and their details.
629
- return self.__jobs
630
-
631
- def start_asyncio_scheduler(self) -> bool:
632
- """
633
- Start the AsyncIOScheduler specifically.
634
-
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.
637
-
638
- Returns
639
- -------
640
- bool
641
- True if the AsyncIOScheduler was started successfully, False otherwise.
642
- """
643
-
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.
660
-
661
- This method is designed to be called from async functions and ensures
662
- the AsyncIOScheduler is properly started within an asyncio event loop.
663
-
664
- Returns
665
- -------
666
- bool
667
- True if the AsyncIOScheduler was started successfully, False otherwise.
668
- """
669
-
670
- try:
671
- if not self.__asyncio_scheduler.running:
672
- self.__asyncio_scheduler.start()
673
- return True
674
- except Exception:
675
- return False
676
-
677
- def is_asyncio_scheduler_running(self) -> bool:
678
- """
679
- Check if the AsyncIOScheduler is currently running.
680
-
681
- Returns
682
- -------
683
- bool
684
- True if the AsyncIOScheduler is running, False otherwise.
685
- """
686
-
687
- return self.__asyncio_scheduler.running if self.__asyncio_scheduler else False
688
-
689
- def get_scheduler_status(self) -> dict:
690
- """
691
- Get the status of all schedulers.
692
-
693
- Returns
694
- -------
695
- dict
696
- A dictionary with the running status of each scheduler type.
697
- """
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
- }
492
+ return self.__jobs