orionis 0.522.0__py3-none-any.whl → 0.524.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.
@@ -18,171 +18,6 @@ class BaseScheduler(IBaseScheduler):
18
18
  # Finalize Global Scheduler at a specific time
19
19
  FINALIZE_AT: datetime = None
20
20
 
21
- def pauseAt(self, timezone: str = None) -> datetime:
22
- """
23
- Retrieves the datetime at which the global scheduler should be paused.
24
-
25
- This method returns the `PAUSE_AT` attribute as a timezone-aware datetime object.
26
- If `PAUSE_AT` is not set (i.e., `None`), the method will return `None`.
27
- If a timezone is provided, it converts the naive datetime to a timezone-aware
28
- datetime using the specified timezone. If no timezone is provided, the naive
29
- datetime is returned as is.
30
-
31
- Parameters
32
- ----------
33
- timezone : str, optional
34
- The name of the timezone to use for converting the naive datetime
35
- to a timezone-aware datetime. For example, "UTC" or "America/New_York".
36
- If not provided, the method returns the naive datetime.
37
-
38
- Returns
39
- -------
40
- datetime or None
41
- - A timezone-aware datetime object representing the pause time of the scheduler
42
- if `PAUSE_AT` is set and a valid timezone is provided.
43
- - A naive datetime object representing the pause time if `PAUSE_AT` is set
44
- but no timezone is provided.
45
- - `None` if `PAUSE_AT` is not set.
46
-
47
- Notes
48
- -----
49
- - The `PAUSE_AT` attribute is expected to be a naive datetime object.
50
- - The method uses the `pytz` library to localize the naive datetime to the specified timezone.
51
- - If the `PAUSE_AT` attribute is `None`, the method will return `None` without performing any conversion.
52
- """
53
-
54
- # Retrieve the naive datetime value for the pause time
55
- dt_naive = self.PAUSE_AT
56
-
57
- # If no pause time is set, return None
58
- if dt_naive is None:
59
- return None
60
-
61
- # If no timezone is provided, return the naive datetime as is
62
- if timezone is None:
63
- return dt_naive
64
-
65
- # Import the pytz library for timezone handling
66
- import pytz
67
-
68
- # Get the specified timezone using pytz
69
- # This will raise an exception if the timezone string is invalid
70
- tz = pytz.timezone(timezone)
71
-
72
- # Convert the naive datetime to a timezone-aware datetime
73
- # This ensures the datetime is localized to the specified timezone
74
- return tz.localize(dt_naive)
75
-
76
- def resumeAt(self, timezone: str = None) -> datetime:
77
- """
78
- Retrieves the datetime at which the global scheduler should be resumed.
79
-
80
- This method returns the `RESUME_AT` attribute as a timezone-aware datetime object.
81
- If `RESUME_AT` is not set (i.e., `None`), the method will return `None`.
82
- If a timezone is provided, it converts the naive datetime to a timezone-aware
83
- datetime using the specified timezone. If no timezone is provided, the naive
84
- datetime is returned as is.
85
-
86
- Parameters
87
- ----------
88
- timezone : str, optional
89
- The name of the timezone to use for converting the naive datetime
90
- to a timezone-aware datetime. For example, "UTC" or "America/New_York".
91
- If not provided, the method returns the naive datetime.
92
-
93
- Returns
94
- -------
95
- datetime or None
96
- - A timezone-aware datetime object representing the resume time of the scheduler
97
- if `RESUME_AT` is set and a valid timezone is provided.
98
- - A naive datetime object representing the resume time if `RESUME_AT` is set
99
- but no timezone is provided.
100
- - `None` if `RESUME_AT` is not set.
101
-
102
- Notes
103
- -----
104
- - The `RESUME_AT` attribute is expected to be a naive datetime object.
105
- - The method uses the `pytz` library to localize the naive datetime to the specified timezone.
106
- - If the `RESUME_AT` attribute is `None`, the method will return `None` without performing any conversion.
107
- """
108
-
109
- # Retrieve the naive datetime value for the resume time
110
- dt_naive = self.RESUME_AT
111
-
112
- # If no resume time is set, return None
113
- if dt_naive is None:
114
- return None
115
-
116
- # If no timezone is provided, return the naive datetime as is
117
- if timezone is None:
118
- return dt_naive
119
-
120
- # Import the pytz library for timezone handling
121
- import pytz
122
-
123
- # Get the specified timezone using pytz
124
- # This will raise an exception if the timezone string is invalid
125
- tz = pytz.timezone(timezone)
126
-
127
- # Convert the naive datetime to a timezone-aware datetime
128
- # This ensures the datetime is localized to the specified timezone
129
- return tz.localize(dt_naive)
130
-
131
- def finalizeAt(self, timezone: str = None) -> datetime:
132
- """
133
- Retrieves the datetime at which the global scheduler should be finalized.
134
-
135
- This method returns the `FINALIZE_AT` attribute as a timezone-aware datetime object.
136
- If `FINALIZE_AT` is not set (i.e., `None`), the method will return `None`.
137
- If a timezone is provided, it converts the naive datetime to a timezone-aware
138
- datetime using the specified timezone. If no timezone is provided, the naive
139
- datetime is returned as is.
140
-
141
- Parameters
142
- ----------
143
- timezone : str, optional
144
- The name of the timezone to use for converting the naive datetime
145
- to a timezone-aware datetime. For example, "UTC" or "America/New_York".
146
- If not provided, the method returns the naive datetime.
147
-
148
- Returns
149
- -------
150
- datetime or None
151
- - A timezone-aware datetime object representing the finalize time of the scheduler
152
- if `FINALIZE_AT` is set and a valid timezone is provided.
153
- - A naive datetime object representing the finalize time if `FINALIZE_AT` is set
154
- but no timezone is provided.
155
- - `None` if `FINALIZE_AT` is not set.
156
-
157
- Notes
158
- -----
159
- - The `FINALIZE_AT` attribute is expected to be a naive datetime object.
160
- - The method uses the `pytz` library to localize the naive datetime to the specified timezone.
161
- - If the `FINALIZE_AT` attribute is `None`, the method will return `None` without performing any conversion.
162
- """
163
-
164
- # Retrieve the naive datetime value for the finalize time
165
- dt_naive = self.FINALIZE_AT
166
-
167
- # If no finalize time is set, return None
168
- if dt_naive is None:
169
- return None
170
-
171
- # If no timezone is provided, return the naive datetime as is
172
- if timezone is None:
173
- return dt_naive
174
-
175
- # Import the pytz library for timezone handling
176
- import pytz
177
-
178
- # Get the specified timezone using pytz
179
- # This will raise an exception if the timezone string is invalid
180
- tz = pytz.timezone(timezone)
181
-
182
- # Convert the naive datetime to a timezone-aware datetime
183
- # This ensures the datetime is localized to the specified timezone
184
- return tz.localize(dt_naive)
185
-
186
21
  async def tasks(self, schedule: ISchedule):
187
22
  """
188
23
  Defines and registers scheduled tasks for the application.
@@ -110,21 +110,23 @@ class ScheduleWorkCommand(BaseCommand):
110
110
  if hasattr(scheduler, "onError") and callable(scheduler.onError):
111
111
  schedule_service.setListener(ListeningEvent.SCHEDULER_ERROR, scheduler.onError)
112
112
 
113
+ # If the scheduler has FINALIZE_AT and it is not None
113
114
  if hasattr(scheduler, "FINALIZE_AT") and scheduler.FINALIZE_AT is not None:
114
115
  if not isinstance(scheduler.FINALIZE_AT, datetime):
115
116
  raise CLIOrionisRuntimeError("FINALIZE_AT must be a datetime instance.")
116
- schedule_service.shutdownEverythingAt(scheduler.finalizeAt())
117
+ schedule_service.shutdownEverythingAt(scheduler.FINALIZE_AT)
117
118
 
119
+ # If the scheduler has RESUME_AT and it is not None
118
120
  if hasattr(scheduler, "RESUME_AT") and scheduler.RESUME_AT is not None:
119
121
  if not isinstance(scheduler.RESUME_AT, datetime):
120
122
  raise CLIOrionisRuntimeError("RESUME_AT must be a datetime instance.")
121
- schedule_service.resumeEverythingAt(scheduler.resumeAt())
123
+ schedule_service.resumeEverythingAt(scheduler.RESUME_AT)
122
124
 
123
- # Check if the scheduler has specific pause, resume, and finalize times
125
+ # If the scheduler has PAUSE_AT and it is not None
124
126
  if hasattr(scheduler, "PAUSE_AT") and scheduler.PAUSE_AT is not None:
125
127
  if not isinstance(scheduler.PAUSE_AT, datetime):
126
128
  raise CLIOrionisRuntimeError("PAUSE_AT must be a datetime instance.")
127
- schedule_service.pauseEverythingAt(scheduler.pauseAt())
129
+ schedule_service.pauseEverythingAt(scheduler.PAUSE_AT)
128
130
 
129
131
  # Start the scheduler worker asynchronously
130
132
  await schedule_service.start()
@@ -1,7 +1,7 @@
1
1
  import asyncio
2
2
  from datetime import datetime
3
3
  import logging
4
- from typing import Dict, List, Optional, Union
4
+ from typing import Any, Awaitable, Callable, Dict, List, Optional, Union
5
5
  import pytz
6
6
  from apscheduler.triggers.date import DateTrigger
7
7
  from apscheduler.events import (
@@ -30,7 +30,9 @@ from orionis.console.entities.job_executed import JobExecuted
30
30
  from orionis.console.entities.job_max_instances import JobMaxInstances
31
31
  from orionis.console.entities.job_missed import JobMissed
32
32
  from orionis.console.entities.job_modified import JobModified
33
+ from orionis.console.entities.job_pause import JobPause
33
34
  from orionis.console.entities.job_removed import JobRemoved
35
+ from orionis.console.entities.job_resume import JobResume
34
36
  from orionis.console.entities.job_submitted import JobSubmitted
35
37
  from orionis.console.entities.scheduler_paused import SchedulerPaused
36
38
  from orionis.console.entities.scheduler_resumed import SchedulerResumed
@@ -40,7 +42,6 @@ from orionis.console.enums.listener import ListeningEvent
40
42
  from orionis.console.enums.event import Event as EventEntity
41
43
  from orionis.console.exceptions import CLIOrionisRuntimeError
42
44
  from orionis.console.exceptions.cli_orionis_value_error import CLIOrionisValueError
43
- from orionis.console.output.contracts.console import IConsole
44
45
  from orionis.foundation.contracts.application import IApplication
45
46
  from orionis.services.log.contracts.log_service import ILogger
46
47
 
@@ -109,6 +110,9 @@ class Scheduler(ISchedule):
109
110
  # Initialize the listeners dictionary to manage event listeners.
110
111
  self.__listeners: Dict[str, callable] = {}
111
112
 
113
+ # Initialize set to track jobs paused by pauseEverythingAt
114
+ self.__paused_by_pause_everything: set = set()
115
+
112
116
  # Add this line to the existing __init__ method
113
117
  self._stop_event: Optional[asyncio.Event] = None
114
118
 
@@ -132,6 +136,9 @@ class Scheduler(ISchedule):
132
136
  tz = pytz.timezone(self.__app.config("app.timezone", "UTC"))
133
137
  now = datetime.now(tz)
134
138
 
139
+ # Log the timezone assignment for debugging purposes
140
+ self.__logger.info(f"Timezone assigned to the scheduler: {self.__app.config("app.timezone", "UTC")}")
141
+
135
142
  # Format the current time as a string
136
143
  return now.strftime("%Y-%m-%d %H:%M:%S")
137
144
 
@@ -312,8 +319,6 @@ class Scheduler(ISchedule):
312
319
  """
313
320
 
314
321
  self.__scheduler.add_listener(self.__startedListener, EVENT_SCHEDULER_STARTED)
315
- self.__scheduler.add_listener(self.__pausedListener, EVENT_SCHEDULER_PAUSED)
316
- self.__scheduler.add_listener(self.__resumedListener, EVENT_SCHEDULER_RESUMED)
317
322
  self.__scheduler.add_listener(self.__shutdownListener, EVENT_SCHEDULER_SHUTDOWN)
318
323
  self.__scheduler.add_listener(self.__errorListener, EVENT_JOB_ERROR)
319
324
  self.__scheduler.add_listener(self.__submittedListener, EVENT_JOB_SUBMITTED)
@@ -378,19 +383,36 @@ class Scheduler(ISchedule):
378
383
 
379
384
  # Try to get the running event loop
380
385
  try:
381
- loop = asyncio.get_running_loop()
382
- loop.create_task(listener(event_data, self))
386
+
387
+ try:
388
+
389
+ # Try to get the current running event loop
390
+ loop = asyncio.get_running_loop()
391
+ loop.create_task(listener(event_data, self))
392
+
393
+ except RuntimeError:
394
+
395
+ # If no event loop is running, create a new one
396
+ loop = asyncio.new_event_loop()
397
+ asyncio.set_event_loop(loop)
398
+ loop.create_task(listener(event_data, self))
383
399
 
384
400
  # If no event loop is running, log a warning instead of creating one
385
401
  except RuntimeError:
386
402
 
403
+ # Message indicating that the listener could not be invoked
404
+ error_msg = f"Cannot run listener for '{scheduler_event}': no event loop running"
405
+
406
+ # Log the error message
407
+ self.__logger.error(error_msg)
408
+
387
409
  # Raise an error to inform the caller that the listener could not be invoked
388
- raise CLIOrionisRuntimeError(
389
- f"Cannot run async listener for '{scheduler_event}': no event loop running"
390
- )
410
+ raise CLIOrionisRuntimeError(error_msg)
391
411
 
392
412
  # Otherwise, invoke the listener directly as a regular function
393
413
  else:
414
+
415
+ # Call the regular listener function directly
394
416
  listener(event_data, self)
395
417
 
396
418
  except Exception as e:
@@ -399,10 +421,14 @@ class Scheduler(ISchedule):
399
421
  if isinstance(e, CLIOrionisRuntimeError):
400
422
  raise e
401
423
 
424
+ # Construct the error message
425
+ error_msg = f"An error occurred while invoking the listener for event '{scheduler_event}': {str(e)}"
426
+
427
+ # Log the error message
428
+ self.__logger.error(error_msg)
429
+
402
430
  # Raise a runtime error if listener invocation fails
403
- raise CLIOrionisRuntimeError(
404
- f"An error occurred while invoking the listener for event '{scheduler_event}': {str(e)}"
405
- )
431
+ raise CLIOrionisRuntimeError(error_msg)
406
432
 
407
433
  def __taskCallableListener(
408
434
  self,
@@ -472,19 +498,36 @@ class Scheduler(ISchedule):
472
498
 
473
499
  # Try to get the running event loop
474
500
  try:
475
- loop = asyncio.get_running_loop()
476
- loop.create_task(listener_method(event_data, self))
501
+
502
+ try:
503
+
504
+ # Try to get the current running event loop
505
+ loop = asyncio.get_running_loop()
506
+ loop.create_task(listener_method(event_data, self))
507
+
508
+ except RuntimeError:
509
+
510
+ # If no event loop is running, create a new one
511
+ loop = asyncio.new_event_loop()
512
+ asyncio.set_event_loop(loop)
513
+ loop.create_task(listener_method(event_data, self))
477
514
 
478
515
  # If no event loop is running, log a warning
479
516
  except RuntimeError:
480
517
 
518
+ # Construct the error message
519
+ error_msg = f"Cannot run async listener for '{scheduler_event}' on job '{event_data.job_id}': no event loop running"
520
+
521
+ # Log the error message
522
+ self.__logger.error(error_msg)
523
+
481
524
  # Raise an error to inform the caller that the listener could not be invoked
482
- raise CLIOrionisRuntimeError(
483
- f"Cannot run async listener for '{scheduler_event}' on job '{event_data.job_id}': no event loop running"
484
- )
525
+ raise CLIOrionisRuntimeError(error_msg)
485
526
 
486
527
  # Call the regular listener method directly
487
528
  else:
529
+
530
+ # Invoke the listener method directly
488
531
  listener_method(event_data, self)
489
532
 
490
533
  except Exception as e:
@@ -493,10 +536,14 @@ class Scheduler(ISchedule):
493
536
  if isinstance(e, CLIOrionisRuntimeError):
494
537
  raise e
495
538
 
539
+ # Construct the error message
540
+ error_msg = (f"An error occurred while invoking the listener for event '{scheduler_event}' on job '{event_data.job_id}': {str(e)}")
541
+
542
+ # Log the error message
543
+ self.__logger.error(error_msg)
544
+
496
545
  # Raise a runtime error if listener invocation fails
497
- raise CLIOrionisRuntimeError(
498
- f"An error occurred while invoking the listener for event '{scheduler_event}' on job '{event_data.job_id}': {str(e)}"
499
- )
546
+ raise CLIOrionisRuntimeError(error_msg)
500
547
 
501
548
  def __startedListener(
502
549
  self,
@@ -526,9 +573,6 @@ class Scheduler(ISchedule):
526
573
  # Get the current time in the configured timezone
527
574
  now = self.__getCurrentTime()
528
575
 
529
- # Log an informational message indicating that the scheduler has started
530
- self.__logger.info(f"Orionis Scheduler started successfully at {now}.")
531
-
532
576
  # Display a start message for the scheduler worker on the rich console
533
577
  # Add a blank line for better formatting
534
578
  self.__rich_console.line()
@@ -553,79 +597,8 @@ class Scheduler(ISchedule):
553
597
  # Check if a listener is registered for the scheduler started event
554
598
  self.__globalCallableListener(event, ListeningEvent.SCHEDULER_STARTED)
555
599
 
556
- def __pausedListener(
557
- self,
558
- event: SchedulerPaused
559
- ) -> None:
560
- """
561
- Handle the scheduler paused event for logging and invoking registered listeners.
562
-
563
- This method is triggered when the scheduler is paused. It logs an informational
564
- message indicating that the scheduler has been paused successfully and displays
565
- a formatted message on the rich console. If a listener is registered for the
566
- scheduler paused event, it invokes the listener with the event details.
567
-
568
- Parameters
569
- ----------
570
- event : SchedulerPaused
571
- An event object containing details about the scheduler paused event.
572
-
573
- Returns
574
- -------
575
- None
576
- This method does not return any value. It performs logging, displays
577
- a message on the console, and invokes any registered listener for the
578
- scheduler paused event.
579
- """
580
-
581
- # Get the current time in the configured timezone
582
- now = self.__getCurrentTime()
583
-
584
- # Create a paused message
585
- message = f"Orionis Scheduler paused successfully at {now}."
586
-
587
- # Log an informational message indicating that the scheduler has been paused
588
- self.__logger.info(message)
589
-
590
- # Check if a listener is registered for the scheduler paused event
591
- self.__globalCallableListener(event, ListeningEvent.SCHEDULER_PAUSED)
592
-
593
- def __resumedListener(
594
- self,
595
- event: SchedulerResumed
596
- ) -> None:
597
- """
598
- Handle the scheduler resumed event for logging and invoking registered listeners.
599
-
600
- This method is triggered when the scheduler resumes from a paused state. It logs an informational
601
- message indicating that the scheduler has resumed successfully and displays a formatted message
602
- on the rich console. If a listener is registered for the scheduler resumed event, it invokes
603
- the listener with the event details.
604
-
605
- Parameters
606
- ----------
607
- event : SchedulerResumed
608
- An event object containing details about the scheduler resumed event.
609
-
610
- Returns
611
- -------
612
- None
613
- This method does not return any value. It performs logging, displays
614
- a message on the console, and invokes any registered listener for the
615
- scheduler resumed event.
616
- """
617
-
618
- # Get the current time in the configured timezone
619
- now = self.__getCurrentTime()
620
-
621
- # Create a resumed message
622
- message = f"Orionis Scheduler resumed successfully at {now}."
623
-
624
- # Log an informational message indicating that the scheduler has resumed
625
- self.__logger.info(message)
626
-
627
- # Check if a listener is registered for the scheduler resumed event
628
- self.__globalCallableListener(event, ListeningEvent.SCHEDULER_RESUMED)
600
+ # Log an informational message indicating that the scheduler has started
601
+ self.__logger.info(f"Orionis Scheduler started successfully at {now}.")
629
602
 
630
603
  def __shutdownListener(
631
604
  self,
@@ -655,15 +628,12 @@ class Scheduler(ISchedule):
655
628
  # Get the current time in the configured timezone
656
629
  now = self.__getCurrentTime()
657
630
 
658
- # Create a shutdown message
659
- message = f"Orionis Scheduler shut down successfully at {now}."
660
-
661
- # Log an informational message indicating that the scheduler has shut down
662
- self.__logger.info(message)
663
-
664
631
  # Check if a listener is registered for the scheduler shutdown event
665
632
  self.__globalCallableListener(event, ListeningEvent.SCHEDULER_SHUTDOWN)
666
633
 
634
+ # Log an informational message indicating that the scheduler has shut down
635
+ self.__logger.info(f"Orionis Scheduler shut down successfully at {now}.")
636
+
667
637
  def __errorListener(
668
638
  self,
669
639
  event: JobError
@@ -689,11 +659,8 @@ class Scheduler(ISchedule):
689
659
  and listener invocation for the job error event.
690
660
  """
691
661
 
692
- # Create an error message
693
- message = f"Task {event.job_id} raised an exception: {event.exception}"
694
-
695
662
  # Log an error message indicating that the job raised an exception
696
- self.__logger.error(message)
663
+ self.__logger.error(f"Task {event.job_id} raised an exception: {event.exception}")
697
664
 
698
665
  # If a listener is registered for this job ID, invoke the listener with the event details
699
666
  self.__taskCallableListener(event, ListeningEvent.JOB_ON_FAILURE)
@@ -726,11 +693,8 @@ class Scheduler(ISchedule):
726
693
  and listener invocation for the job submission event.
727
694
  """
728
695
 
729
- # Create a submission message
730
- message = f"Task {event.job_id} submitted to executor."
731
-
732
696
  # Log an informational message indicating that the job has been submitted
733
- self.__logger.info(message)
697
+ self.__logger.info(f"Task {event.job_id} submitted to executor.")
734
698
 
735
699
  # If a listener is registered for this job ID, invoke the listener with the event details
736
700
  self.__taskCallableListener(event, ListeningEvent.JOB_BEFORE)
@@ -761,11 +725,8 @@ class Scheduler(ISchedule):
761
725
  and listener invocation for the job execution event.
762
726
  """
763
727
 
764
- # Create an execution message
765
- message = f"Task {event.job_id} executed."
766
-
767
728
  # Log an informational message indicating that the job has been executed
768
- self.__logger.info(message)
729
+ self.__logger.info(f"Task {event.job_id} executed.")
769
730
 
770
731
  # If a listener is registered for this job ID, invoke the listener with the event details
771
732
  self.__taskCallableListener(event, ListeningEvent.JOB_AFTER)
@@ -796,11 +757,8 @@ class Scheduler(ISchedule):
796
757
  and listener invocation for the missed job event.
797
758
  """
798
759
 
799
- # Create a missed job message
800
- message = f"Task {event.job_id} was missed. It was scheduled to run at {event.scheduled_run_time}."
801
-
802
760
  # Log a warning indicating that the job was missed
803
- self.__logger.warning(message)
761
+ self.__logger.warning(f"Task {event.job_id} was missed. It was scheduled to run at {event.scheduled_run_time}.")
804
762
 
805
763
  # If a listener is registered for this job ID, invoke the listener with the event details
806
764
  self.__taskCallableListener(event, ListeningEvent.JOB_ON_MISSED)
@@ -831,11 +789,8 @@ class Scheduler(ISchedule):
831
789
  and listener invocation for the job max instances event.
832
790
  """
833
791
 
834
- # Create a max instances error message
835
- message = f"Task {event.job_id} exceeded maximum instances"
836
-
837
792
  # Log an error message indicating that the job exceeded maximum instances
838
- self.__logger.error(message)
793
+ self.__logger.error(f"Task {event.job_id} exceeded maximum instances")
839
794
 
840
795
  # If a listener is registered for this job ID, invoke the listener with the event details
841
796
  self.__taskCallableListener(event, ListeningEvent.JOB_ON_MAXINSTANCES)
@@ -866,16 +821,12 @@ class Scheduler(ISchedule):
866
821
  and listener invocation for the job modified event.
867
822
  """
868
823
 
869
- # Create a modified message
870
- message = f"Task {event.job_id} has been modified."
871
-
872
- # Log an informational message indicating that the job has been modified
873
- self.__logger.info(message)
874
-
875
824
  # If a listener is registered for this job ID, invoke the listener with the event details
876
825
  if event.next_run_time is None:
826
+ self.__logger.info(f"Task {event.job_id} has been paused.")
877
827
  self.__taskCallableListener(event, ListeningEvent.JOB_ON_PAUSED)
878
828
  else:
829
+ self.__logger.info(f"Task {event.job_id} has been resumed.")
879
830
  self.__taskCallableListener(event, ListeningEvent.JOB_ON_RESUMED)
880
831
 
881
832
  def __removedListener(
@@ -903,11 +854,8 @@ class Scheduler(ISchedule):
903
854
  listener for the job removal event.
904
855
  """
905
856
 
906
- # Create a message indicating that the job has been removed
907
- message = f"Task {event.job_id} has been removed."
908
-
909
857
  # Log the removal of the job
910
- self.__logger.info(message)
858
+ self.__logger.info(f"Task {event.job_id} has been removed.")
911
859
 
912
860
  # If a listener is registered for this job ID, invoke the listener with the event details
913
861
  self.__taskCallableListener(event, ListeningEvent.JOB_ON_REMOVED)
@@ -958,12 +906,19 @@ class Scheduler(ISchedule):
958
906
  if entity.listener:
959
907
  self.setListener(signature, entity.listener)
960
908
 
909
+ # Log the successful loading of the scheduled event
910
+ self.__logger.debug(f"Scheduled event '{signature}' loaded successfully.")
911
+
961
912
  except Exception as e:
962
913
 
914
+ # Construct the error message
915
+ error_msg = f"Failed to load scheduled event '{signature}': {str(e)}"
916
+
917
+ # Log the error message
918
+ self.__logger.error(error_msg)
919
+
963
920
  # Raise a runtime error if loading the scheduled event fails
964
- raise CLIOrionisRuntimeError(
965
- f"Failed to load scheduled event '{signature}': {str(e)}"
966
- ) from e
921
+ raise CLIOrionisRuntimeError(error_msg)
967
922
 
968
923
  def setListener(
969
924
  self,
@@ -1015,6 +970,53 @@ class Scheduler(ISchedule):
1015
970
  # Register the listener for the specified event in the internal listeners dictionary
1016
971
  self.__listeners[event] = listener
1017
972
 
973
+ def wrapAsyncFunction(
974
+ self,
975
+ func: Callable[..., Awaitable[Any]]
976
+ ) -> Callable[..., Any]:
977
+ """
978
+ Wrap an asynchronous function to be called in a synchronous context.
979
+
980
+ This method takes an asynchronous function (a coroutine) and returns a synchronous
981
+ wrapper function that can be called in a non-async context. The wrapper function
982
+ ensures that the asynchronous function is executed within the appropriate event loop.
983
+
984
+ Parameters
985
+ ----------
986
+ func : Callable[..., Awaitable[Any]]
987
+ The asynchronous function (coroutine) to be wrapped. This function should be
988
+ defined using the `async def` syntax and return an awaitable object.
989
+
990
+ Returns
991
+ -------
992
+ Callable[..., Any]
993
+ A synchronous wrapper function that can be called in a non-async context.
994
+ When invoked, this wrapper will execute the original asynchronous function
995
+ within the appropriate event loop.
996
+
997
+ Raises
998
+ ------
999
+ ValueError
1000
+ If the provided `func` is not an asynchronous function (coroutine).
1001
+ """
1002
+
1003
+ # Define a synchronous wrapper function
1004
+ def sync_wrapper(*args, **kwargs) -> Any:
1005
+ try:
1006
+ # Try to get the current event loop
1007
+ loop = asyncio.get_event_loop()
1008
+ if loop.is_running():
1009
+ # If the loop is already running, create a task for the coroutine
1010
+ return asyncio.create_task(func(*args, **kwargs))
1011
+ else:
1012
+ # If the loop is not running, run the coroutine until complete
1013
+ return loop.run_until_complete(func(*args, **kwargs))
1014
+ except RuntimeError:
1015
+ # If no event loop exists, create a new one and run the coroutine
1016
+ return asyncio.run(func(*args, **kwargs))
1017
+
1018
+ return sync_wrapper
1019
+
1018
1020
  def pauseEverythingAt(
1019
1021
  self,
1020
1022
  at: datetime
@@ -1050,21 +1052,68 @@ class Scheduler(ISchedule):
1050
1052
  if not isinstance(at, datetime):
1051
1053
  raise ValueError("The 'at' parameter must be a datetime object.")
1052
1054
 
1053
- # Define a function to pause the scheduler
1054
- def schedule_pause():
1055
+ # Define an async function to pause the scheduler
1056
+ async def schedule_pause():
1057
+
1058
+ # Only pause jobs if the scheduler is currently running
1055
1059
  if self.isRunning():
1056
- self.__scheduler.pause()
1060
+
1061
+ # Clear the set of previously paused jobs
1062
+ self.__paused_by_pause_everything.clear()
1063
+
1064
+ # Get all jobs from the scheduler
1065
+ all_jobs = self.__scheduler.get_jobs()
1066
+
1067
+ # Filter out system jobs (pause, resume, shutdown tasks)
1068
+ system_job_ids = {
1069
+ "scheduler_pause_at",
1070
+ "scheduler_resume_at",
1071
+ "scheduler_shutdown_at"
1072
+ }
1073
+
1074
+ # Pause only user jobs, not system jobs
1075
+ for job in all_jobs:
1076
+
1077
+ # Pause the job only if it is not a system job
1078
+ if job.id not in system_job_ids:
1079
+
1080
+ # Pause the job and add its ID to the set of paused jobs
1081
+ try:
1082
+
1083
+ # Pause the job in the scheduler
1084
+ self.__scheduler.pause_job(job.id)
1085
+ self.__paused_by_pause_everything.add(job.id)
1086
+
1087
+ # Execute the task callable listener
1088
+ self.__globalCallableListener(SchedulerPaused(EVENT_SCHEDULER_PAUSED), ListeningEvent.SCHEDULER_PAUSED)
1089
+
1090
+ # Get the current time in the configured timezone
1091
+ now = self.__getCurrentTime()
1092
+
1093
+ # Log an informational message indicating that the scheduler has resumed
1094
+ self.__logger.info(f"Orionis Scheduler resumed successfully at {now}.")
1095
+
1096
+ except Exception as e:
1097
+
1098
+ # Log a warning if pausing a job fails, but continue with others
1099
+ self.__logger.warning(f"Failed to pause job '{job.id}': {str(e)}")
1100
+
1101
+ # Log that all user jobs have been paused
1102
+ self.__logger.info("All user jobs have been paused. System jobs remain active.")
1057
1103
 
1058
1104
  try:
1105
+
1059
1106
  # Remove any existing pause job to avoid conflicts
1060
1107
  try:
1061
1108
  self.__scheduler.remove_job("scheduler_pause_at")
1109
+
1110
+ # If the job doesn't exist, it's fine to proceed
1062
1111
  except:
1063
- pass # If the job doesn't exist, it's fine to proceed
1112
+ pass
1064
1113
 
1065
1114
  # Add a job to the scheduler to pause it at the specified datetime
1066
1115
  self.__scheduler.add_job(
1067
- func=schedule_pause, # Function to pause the scheduler
1116
+ func=self.wrapAsyncFunction(schedule_pause), # Function to pause the scheduler
1068
1117
  trigger=DateTrigger(run_date=at), # Trigger type is 'date' for one-time execution
1069
1118
  id="scheduler_pause_at", # Unique job ID for pausing the scheduler
1070
1119
  name="Pause Scheduler", # Descriptive name for the job
@@ -1114,22 +1163,57 @@ class Scheduler(ISchedule):
1114
1163
  if not isinstance(at, datetime):
1115
1164
  raise ValueError("The 'at' parameter must be a datetime object.")
1116
1165
 
1117
- # Define a function to resume the scheduler
1118
- def schedule_resume():
1166
+ # Define an async function to resume the scheduler
1167
+ async def schedule_resume():
1168
+
1169
+ # Only resume jobs if the scheduler is currently running
1119
1170
  if self.isRunning():
1120
- self.__scheduler.resume()
1171
+
1172
+ # Resume only jobs that were paused by pauseEverythingAt
1173
+ if self.__paused_by_pause_everything:
1174
+
1175
+ # Iterate through the set of paused job IDs and resume each one
1176
+ for job_id in list(self.__paused_by_pause_everything):
1177
+
1178
+ try:
1179
+
1180
+ # Resume the job and log the action
1181
+ self.__scheduler.resume_job(job_id)
1182
+ self.__logger.info(f"User job '{job_id}' has been resumed.")
1183
+
1184
+ # Execute the task callable listener
1185
+ self.__globalCallableListener(SchedulerResumed(EVENT_SCHEDULER_RESUMED), ListeningEvent.SCHEDULER_RESUMED)
1186
+
1187
+ # Get the current time in the configured timezone
1188
+ now = self.__getCurrentTime()
1189
+
1190
+ # Log an informational message indicating that the scheduler has been paused
1191
+ self.__logger.info(f"Orionis Scheduler paused successfully at {now}.")
1192
+
1193
+ except Exception as e:
1194
+
1195
+ # Log a warning if resuming a job fails, but continue with others
1196
+ self.__logger.warning(f"Failed to resume job '{job_id}': {str(e)}")
1197
+
1198
+ # Clear the set after resuming all jobs
1199
+ self.__paused_by_pause_everything.clear()
1200
+
1201
+ # Log that all previously paused jobs have been resumed
1202
+ self.__logger.info("All previously paused user jobs have been resumed.")
1121
1203
 
1122
1204
  try:
1123
1205
 
1124
1206
  # Remove any existing resume job to avoid conflicts
1125
1207
  try:
1126
1208
  self.__scheduler.remove_job("scheduler_resume_at")
1209
+
1210
+ # If the job doesn't exist, it's fine to proceed
1127
1211
  except:
1128
- pass # If the job doesn't exist, it's fine to proceed
1212
+ pass
1129
1213
 
1130
1214
  # Add a job to the scheduler to resume it at the specified datetime
1131
1215
  self.__scheduler.add_job(
1132
- func=schedule_resume, # Function to resume the scheduler
1216
+ func=self.wrapAsyncFunction(schedule_resume), # Function to resume the scheduler
1133
1217
  trigger=DateTrigger(run_date=at), # Trigger type is 'date' for one-time execution
1134
1218
  id="scheduler_resume_at", # Unique job ID for resuming the scheduler
1135
1219
  name="Resume Scheduler", # Descriptive name for the job
@@ -1190,8 +1274,13 @@ class Scheduler(ISchedule):
1190
1274
 
1191
1275
  # Define a function to shut down the scheduler
1192
1276
  def schedule_shutdown():
1277
+
1278
+ # Only shut down the scheduler if it is currently running
1193
1279
  if self.isRunning():
1280
+
1281
+ # Execute the shutdown process
1194
1282
  self.__scheduler.shutdown(wait=wait)
1283
+
1195
1284
  # Signal the stop event to break the wait in start()
1196
1285
  if self._stop_event and not self._stop_event.is_set():
1197
1286
  self._stop_event.set()
@@ -1201,12 +1290,14 @@ class Scheduler(ISchedule):
1201
1290
  # Remove any existing shutdown job to avoid conflicts
1202
1291
  try:
1203
1292
  self.__scheduler.remove_job("scheduler_shutdown_at")
1293
+
1294
+ # If the job doesn't exist, it's fine to proceed
1204
1295
  except:
1205
- pass # If the job doesn't exist, it's fine to proceed
1296
+ pass
1206
1297
 
1207
1298
  # Add a job to the scheduler to shut it down at the specified datetime
1208
1299
  self.__scheduler.add_job(
1209
- func=schedule_shutdown, # Function to shut down the scheduler
1300
+ func=schedule_shutdown, # Function to shut down the scheduler
1210
1301
  trigger=DateTrigger(run_date=at), # Trigger type is 'date' for one-time execution
1211
1302
  id="scheduler_shutdown_at", # Unique job ID for shutting down the scheduler
1212
1303
  name="Shutdown Scheduler", # Descriptive name for the job
@@ -1260,7 +1351,7 @@ class Scheduler(ISchedule):
1260
1351
  self.__scheduler.start()
1261
1352
 
1262
1353
  # Log that the scheduler is now active and waiting for events
1263
- self.__logger.info("Scheduler is now active and waiting for events...")
1354
+ self.__logger.info("Orionis Scheduler is now active and waiting for events...")
1264
1355
 
1265
1356
  try:
1266
1357
  # Wait for the stop event to be set, which signals a shutdown
@@ -1565,8 +1656,18 @@ class Scheduler(ISchedule):
1565
1656
  False if no pause job was found or an error occurred during the cancellation process.
1566
1657
  """
1567
1658
  try:
1659
+
1660
+ # Remove any listener associated with the pause event
1661
+ listener = ListeningEvent.SCHEDULER_PAUSED.value
1662
+ if listener in self.__listeners:
1663
+ del self.__listeners[listener]
1664
+
1568
1665
  # Attempt to remove the pause job with the specific ID
1569
- self.__scheduler.remove_job("scheduler_pause_at")
1666
+ # if it exists
1667
+ try:
1668
+ self.__scheduler.remove_job("scheduler_pause_at")
1669
+ finally:
1670
+ pass
1570
1671
 
1571
1672
  # Log the successful cancellation of the pause operation
1572
1673
  self.__logger.info("Scheduled pause operation cancelled.")
@@ -1593,8 +1694,18 @@ class Scheduler(ISchedule):
1593
1694
  False if no resume job was found or an error occurred during the cancellation process.
1594
1695
  """
1595
1696
  try:
1697
+
1698
+ # Remove any listener associated with the resume event
1699
+ listener = ListeningEvent.SCHEDULER_RESUMED.value
1700
+ if listener in self.__listeners:
1701
+ del self.__listeners[listener]
1702
+
1596
1703
  # Attempt to remove the resume job with the specific ID
1597
- self.__scheduler.remove_job("scheduler_resume_at")
1704
+ # if it exists
1705
+ try:
1706
+ self.__scheduler.remove_job("scheduler_resume_at")
1707
+ finally:
1708
+ pass
1598
1709
 
1599
1710
  # Log the successful cancellation of the resume operation
1600
1711
  self.__logger.info("Scheduled resume operation cancelled.")
@@ -1622,8 +1733,18 @@ class Scheduler(ISchedule):
1622
1733
  False if no shutdown job was found or an error occurred during the cancellation process.
1623
1734
  """
1624
1735
  try:
1736
+
1737
+ # Remove any listener associated with the shutdown event
1738
+ listener = ListeningEvent.SCHEDULER_SHUTDOWN.value
1739
+ if listener in self.__listeners:
1740
+ del self.__listeners[listener]
1741
+
1625
1742
  # Attempt to remove the shutdown job with the specific ID
1626
- self.__scheduler.remove_job("scheduler_shutdown_at")
1743
+ # if it exists
1744
+ try:
1745
+ self.__scheduler.remove_job("scheduler_shutdown_at")
1746
+ finally:
1747
+ pass
1627
1748
 
1628
1749
  # Log the successful cancellation of the shutdown operation
1629
1750
  self.__logger.info("Scheduled shutdown operation cancelled.")
@@ -1653,27 +1774,6 @@ class Scheduler(ISchedule):
1653
1774
  # Return the running state of the scheduler
1654
1775
  return self.__scheduler.running
1655
1776
 
1656
- async def waitUntilStopped(self) -> None:
1657
- """
1658
- Wait for the scheduler to stop gracefully.
1659
-
1660
- This method blocks the execution until the scheduler is stopped. It waits for the
1661
- internal stop event to be set, which signals that the scheduler has been shut down.
1662
- This is useful for ensuring that the scheduler completes its operations before
1663
- proceeding with other tasks.
1664
-
1665
- Returns
1666
- -------
1667
- None
1668
- This method does not return any value. It waits until the scheduler is stopped.
1669
- """
1670
-
1671
- # Check if the stop event is initialized
1672
- if self._stop_event:
1673
-
1674
- # Wait for the stop event to be set, signaling the scheduler has stopped
1675
- await self._stop_event.wait()
1676
-
1677
1777
  def forceStop(self) -> None:
1678
1778
  """
1679
1779
  Forcefully stop the scheduler immediately without waiting for jobs to complete.
@@ -1718,6 +1818,7 @@ class Scheduler(ISchedule):
1718
1818
 
1719
1819
  # Check if the stop event exists and has not already been set
1720
1820
  if self._stop_event and not self._stop_event.is_set():
1821
+
1721
1822
  # Get the current asyncio event loop
1722
1823
  loop = asyncio.get_event_loop()
1723
1824
 
@@ -626,62 +626,89 @@ class Application(Container, IApplication):
626
626
  configuration system.
627
627
  """
628
628
 
629
+ # Convert dataclass instances to dictionaries
630
+ from orionis.services.introspection.dataclass.attributes import attributes
631
+
629
632
  # Load app configurator
633
+ if (isinstance(app, type) and issubclass(app, App)):
634
+ app = attributes(app)
630
635
  if not isinstance(app, (App, dict)):
631
636
  raise OrionisTypeError(f"Expected App instance or dict, got {type(app).__name__}")
632
637
  self.loadConfigApp(app)
633
638
 
634
639
  # Load auth configurator
640
+ if (isinstance(auth, type) and issubclass(auth, Auth)):
641
+ auth = attributes(auth)
635
642
  if not isinstance(auth, (Auth, dict)):
636
643
  raise OrionisTypeError(f"Expected Auth instance or dict, got {type(auth).__name__}")
637
644
  self.loadConfigAuth(auth)
638
645
 
639
646
  # Load cache configurator
647
+ if (isinstance(cache, type) and issubclass(cache, Cache)):
648
+ cache = attributes(cache)
640
649
  if not isinstance(cache, (Cache, dict)):
641
650
  raise OrionisTypeError(f"Expected Cache instance or dict, got {type(cache).__name__}")
642
651
  self.loadConfigCache(cache)
643
652
 
644
653
  # Load cors configurator
654
+ if (isinstance(cors, type) and issubclass(cors, Cors)):
655
+ cors = attributes(cors)
645
656
  if not isinstance(cors, (Cors, dict)):
646
657
  raise OrionisTypeError(f"Expected Cors instance or dict, got {type(cors).__name__}")
647
658
  self.loadConfigCors(cors)
648
659
 
649
660
  # Load database configurator
661
+ if (isinstance(database, type) and issubclass(database, Database)):
662
+ database = attributes(database)
650
663
  if not isinstance(database, (Database, dict)):
651
664
  raise OrionisTypeError(f"Expected Database instance or dict, got {type(database).__name__}")
652
665
  self.loadConfigDatabase(database)
653
666
 
654
667
  # Load filesystems configurator
668
+ if (isinstance(filesystems, type) and issubclass(filesystems, Filesystems)):
669
+ filesystems = attributes(filesystems)
655
670
  if not isinstance(filesystems, (Filesystems, dict)):
656
671
  raise OrionisTypeError(f"Expected Filesystems instance or dict, got {type(filesystems).__name__}")
657
672
  self.loadConfigFilesystems(filesystems)
658
673
 
659
674
  # Load logging configurator
675
+ if (isinstance(logging, type) and issubclass(logging, Logging)):
676
+ logging = attributes(logging)
660
677
  if not isinstance(logging, (Logging, dict)):
661
678
  raise OrionisTypeError(f"Expected Logging instance or dict, got {type(logging).__name__}")
662
679
  self.loadConfigLogging(logging)
663
680
 
664
681
  # Load mail configurator
682
+ if (isinstance(mail, type) and issubclass(mail, Mail)):
683
+ mail = attributes(mail)
665
684
  if not isinstance(mail, (Mail, dict)):
666
685
  raise OrionisTypeError(f"Expected Mail instance or dict, got {type(mail).__name__}")
667
686
  self.loadConfigMail(mail)
668
687
 
669
688
  # Load paths configurator
689
+ if (isinstance(path, type) and issubclass(path, Paths)):
690
+ path = attributes(path)
670
691
  if not isinstance(path, (Paths, dict)):
671
692
  raise OrionisTypeError(f"Expected Paths instance or dict, got {type(path).__name__}")
672
693
  self.loadPaths(path)
673
694
 
674
695
  # Load queue configurator
696
+ if (isinstance(queue, type) and issubclass(queue, Queue)):
697
+ queue = attributes(queue)
675
698
  if not isinstance(queue, (Queue, dict)):
676
699
  raise OrionisTypeError(f"Expected Queue instance or dict, got {type(queue).__name__}")
677
700
  self.loadConfigQueue(queue)
678
701
 
679
702
  # Load session configurator
703
+ if (isinstance(session, type) and issubclass(session, Session)):
704
+ session = attributes(session)
680
705
  if not isinstance(session, (Session, dict)):
681
706
  raise OrionisTypeError(f"Expected Session instance or dict, got {type(session).__name__}")
682
707
  self.loadConfigSession(session)
683
708
 
684
709
  # Load testing configurator
710
+ if (isinstance(testing, type) and issubclass(testing, Testing)):
711
+ testing = attributes(testing)
685
712
  if not isinstance(testing, (Testing, dict)):
686
713
  raise OrionisTypeError(f"Expected Testing instance or dict, got {type(testing).__name__}")
687
714
  self.loadConfigTesting(testing)
@@ -767,7 +794,9 @@ class Application(Container, IApplication):
767
794
 
768
795
  # If app is a dict, convert it to App instance
769
796
  if isinstance(app, dict):
770
- app = App(**app)
797
+ app = App(**app).toDict()
798
+ elif isinstance(app, App):
799
+ app = app.toDict()
771
800
 
772
801
  # Store the configuration
773
802
  self.__configurators['app'] = app
@@ -853,7 +882,9 @@ class Application(Container, IApplication):
853
882
 
854
883
  # If auth is a dict, convert it to Auth instance
855
884
  if isinstance(auth, dict):
856
- auth = Auth(**auth)
885
+ auth = Auth(**auth).toDict()
886
+ elif isinstance(auth, Auth):
887
+ auth = auth.toDict()
857
888
 
858
889
  # Store the configuration
859
890
  self.__configurators['auth'] = auth
@@ -939,7 +970,9 @@ class Application(Container, IApplication):
939
970
 
940
971
  # If cache is a dict, convert it to Cache instance
941
972
  if isinstance(cache, dict):
942
- cache = Cache(**cache)
973
+ cache = Cache(**cache).toDict()
974
+ elif isinstance(cache, Cache):
975
+ cache = cache.toDict()
943
976
 
944
977
  # Store the configuration
945
978
  self.__configurators['cache'] = cache
@@ -1025,7 +1058,9 @@ class Application(Container, IApplication):
1025
1058
 
1026
1059
  # If cors is a dict, convert it to Cors instance
1027
1060
  if isinstance(cors, dict):
1028
- cors = Cors(**cors)
1061
+ cors = Cors(**cors).toDict()
1062
+ elif isinstance(cors, Cors):
1063
+ cors = cors.toDict()
1029
1064
 
1030
1065
  # Store the configuration
1031
1066
  self.__configurators['cors'] = cors
@@ -1111,7 +1146,9 @@ class Application(Container, IApplication):
1111
1146
 
1112
1147
  # If database is a dict, convert it to Database instance
1113
1148
  if isinstance(database, dict):
1114
- database = Database(**database)
1149
+ database = Database(**database).toDict()
1150
+ elif isinstance(database, Database):
1151
+ database = database.toDict()
1115
1152
 
1116
1153
  # Store the configuration
1117
1154
  self.__configurators['database'] = database
@@ -1197,7 +1234,9 @@ class Application(Container, IApplication):
1197
1234
 
1198
1235
  # If filesystems is a dict, convert it to Filesystems instance
1199
1236
  if isinstance(filesystems, dict):
1200
- filesystems = Filesystems(**filesystems)
1237
+ filesystems = Filesystems(**filesystems).toDict()
1238
+ elif isinstance(filesystems, Filesystems):
1239
+ filesystems = filesystems.toDict()
1201
1240
 
1202
1241
  # Store the configuration
1203
1242
  self.__configurators['filesystems'] = filesystems
@@ -1283,7 +1322,9 @@ class Application(Container, IApplication):
1283
1322
 
1284
1323
  # If logging is a dict, convert it to Logging instance
1285
1324
  if isinstance(logging, dict):
1286
- logging = Logging(**logging)
1325
+ logging = Logging(**logging).toDict()
1326
+ elif isinstance(logging, Logging):
1327
+ logging = logging.toDict()
1287
1328
 
1288
1329
  # Store the configuration
1289
1330
  self.__configurators['logging'] = logging
@@ -1369,7 +1410,9 @@ class Application(Container, IApplication):
1369
1410
 
1370
1411
  # If mail is a dict, convert it to Mail instance
1371
1412
  if isinstance(mail, dict):
1372
- mail = Mail(**mail)
1413
+ mail = Mail(**mail).toDict()
1414
+ elif isinstance(mail, Mail):
1415
+ mail = mail.toDict()
1373
1416
 
1374
1417
  # Store the configuration
1375
1418
  self.__configurators['mail'] = mail
@@ -1558,7 +1601,12 @@ class Application(Container, IApplication):
1558
1601
  paths.update({
1559
1602
  'root': self.__bootstrap_base_path or str(Path.cwd().resolve())
1560
1603
  })
1561
- paths = Paths(**paths)
1604
+ paths = Paths(**paths).toDict()
1605
+ elif isinstance(paths, Paths):
1606
+ paths = paths.toDict()
1607
+ paths.update({
1608
+ 'root': self.__bootstrap_base_path or str(Path.cwd().resolve())
1609
+ })
1562
1610
 
1563
1611
  # Store the configuration
1564
1612
  self.__configurators['path'] = paths
@@ -1690,7 +1738,9 @@ class Application(Container, IApplication):
1690
1738
 
1691
1739
  # If queue is a dict, convert it to Queue instance
1692
1740
  if isinstance(queue, dict):
1693
- queue = Queue(**queue)
1741
+ queue = Queue(**queue).toDict()
1742
+ elif isinstance(queue, Queue):
1743
+ queue = queue.toDict()
1694
1744
 
1695
1745
  # Store the configuration
1696
1746
  self.__configurators['queue'] = queue
@@ -1776,7 +1826,9 @@ class Application(Container, IApplication):
1776
1826
 
1777
1827
  # If session is a dict, convert it to Session instance
1778
1828
  if isinstance(session, dict):
1779
- session = Session(**session)
1829
+ session = Session(**session).toDict()
1830
+ elif isinstance(session, Session):
1831
+ session = session.toDict()
1780
1832
 
1781
1833
  # Store the configuration
1782
1834
  self.__configurators['session'] = session
@@ -1862,7 +1914,9 @@ class Application(Container, IApplication):
1862
1914
 
1863
1915
  # If testing is a dict, convert it to Testing instance
1864
1916
  if isinstance(testing, dict):
1865
- testing = Testing(**testing)
1917
+ testing = Testing(**testing).toDict()
1918
+ elif isinstance(testing, Testing):
1919
+ testing = testing.toDict()
1866
1920
 
1867
1921
  # Store the configuration
1868
1922
  self.__configurators['testing'] = testing
@@ -92,6 +92,8 @@ class Session(BaseEntity):
92
92
  """
93
93
 
94
94
  # Validate secret_key
95
+ if self.secret_key is None:
96
+ self.secret_key = SecretKey.random()
95
97
  if not isinstance(self.secret_key, str) or not self.secret_key.strip():
96
98
  raise OrionisIntegrityException("Session secret_key must be a non-empty string")
97
99
 
@@ -5,7 +5,7 @@
5
5
  NAME = "orionis"
6
6
 
7
7
  # Current version of the framework
8
- VERSION = "0.522.0"
8
+ VERSION = "0.524.0"
9
9
 
10
10
  # Full name of the author or maintainer of the project
11
11
  AUTHOR = "Raul Mauricio Uñate Castro"
File without changes
@@ -0,0 +1,30 @@
1
+ class DataclassValues:
2
+
3
+ def __call__(self, dataclass_type: type) -> dict:
4
+ """
5
+ Extract attributes and their values from a given dataclass type.
6
+
7
+ This method retrieves all attributes defined in the provided dataclass type,
8
+ excluding special attributes (those whose names start with '__'). It returns
9
+ a dictionary where the keys are the attribute names and the values are the
10
+ corresponding attribute values.
11
+
12
+ Parameters
13
+ ----------
14
+ dataclass_type : type
15
+ The dataclass type from which to extract attributes.
16
+
17
+ Returns
18
+ -------
19
+ dict
20
+ A dictionary containing attribute names as keys and their corresponding
21
+ values as values. Special attributes (those starting with '__') are excluded.
22
+ """
23
+
24
+ # Retrieve all attributes from the dataclass type's __dict__ attribute
25
+ # Exclude special attributes (names starting with '__')
26
+ values = {k: v for k, v in dataclass_type.__dict__.items() if not k.startswith("__")}
27
+ return values
28
+
29
+ # Instantiate the DataclassValues callable
30
+ attributes = DataclassValues()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: orionis
3
- Version: 0.522.0
3
+ Version: 0.524.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
@@ -8,14 +8,14 @@ orionis/console/args/enums/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
8
8
  orionis/console/args/enums/actions.py,sha256=S3T-vWS6DJSGtANrq3od3-90iYAjPvJwaOZ2V02y34c,1222
9
9
  orionis/console/base/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
10
  orionis/console/base/command.py,sha256=OM4xqVgpv_1RZVyVG8BzOHl1sP9FT5mPUwZjMil8IRg,6637
11
- orionis/console/base/scheduler.py,sha256=LFzWUFk07LrcpKFL7sS7exHzTkxFRd1DPDSqDSpRcOk,15157
11
+ orionis/console/base/scheduler.py,sha256=w86p-4KjfMqMcGlQBsmiBASpzv33M-PWLbgYza7Um9g,8030
12
12
  orionis/console/base/scheduler_event_listener.py,sha256=5qWPmf6jmiRwUz6U1ZvpQCG5eovOpeCl0KAb8kKDkfU,3905
13
13
  orionis/console/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
14
  orionis/console/commands/cache.py,sha256=8DsYoRzSBLn0P9qkGVItRbo0R6snWBDBg0_Xa7tmVhs,2322
15
15
  orionis/console/commands/help.py,sha256=zfSw0pYaOnFN-_Ozdn4veBQDYMgSSDY10nPDCi-7tTY,3199
16
16
  orionis/console/commands/publisher.py,sha256=FUg-EUzK7LLXsla10ZUZro8V0Z5S-KjmsaSdRHSSGbA,21381
17
17
  orionis/console/commands/scheduler_list.py,sha256=A2N_mEXEJDHO8DX2TDrL1ROeeRhFSkWD3rCw64Hrf0o,4763
18
- orionis/console/commands/scheduler_work.py,sha256=FHBQ8Ajs1zacuQFXaG-KLVRy07m9FqwyJRNVmp7cDg0,6337
18
+ orionis/console/commands/scheduler_work.py,sha256=mzSFes8Wl1gCf253tNYClij0abT5HlpW1QZVFrU5EXo,6445
19
19
  orionis/console/commands/test.py,sha256=-EmQwFwMBuby3OI9HwqMIwuJzd2CGbWbOqmwrR25sOE,2402
20
20
  orionis/console/commands/version.py,sha256=SUuNDJ40f2uq69OQUmPQXJKaa9Bm_iVRDPmBd7zc1Yc,3658
21
21
  orionis/console/commands/workflow.py,sha256=NYOmjTSvm2o6AE4h9LSTZMFSYPQreNmEJtronyOxaYk,2451
@@ -81,7 +81,7 @@ orionis/console/request/cli_request.py,sha256=7-sgYmNUCipuHLVAwWLJiHv0cJCDmsM1Lu
81
81
  orionis/console/tasks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
82
82
  orionis/console/tasks/event.py,sha256=l4J-HEPaj1mxB_PYQMgG9dRHUe01wUag8fKLLnR2N2M,164395
83
83
  orionis/console/tasks/listener.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
84
- orionis/console/tasks/schedule.py,sha256=Lpm_P0Brw8XHiqA-Me9SMevNlo0CzUf-rrt2PKTFW_I,72389
84
+ orionis/console/tasks/schedule.py,sha256=gaH9YSoYDQOTTktEaFyk5OIFUQz-jtkpzQJfKHs6hf4,77213
85
85
  orionis/container/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
86
86
  orionis/container/container.py,sha256=aF_b6lTUpG4YCo9yFJEzsntTdIzgMMXFW5LyWqAJVBQ,87987
87
87
  orionis/container/context/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -123,7 +123,7 @@ orionis/failure/contracts/handler.py,sha256=4N9yMkMgdhtHbRGAyCtuTx3GmkPP74vhqdRL
123
123
  orionis/failure/entities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
124
124
  orionis/failure/entities/throwable.py,sha256=ogys062uhim5QMYU62ezlnumRAnYQlUf_vZvQY47S3U,1227
125
125
  orionis/foundation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
126
- orionis/foundation/application.py,sha256=GmfLnpWuhX_6d1gzUk1v7jy_R72XpLJdA-hEOrXQn_Q,83792
126
+ orionis/foundation/application.py,sha256=OOe10s0Dpo3uC4L3xrhjRitaZo7ukaFJaRSJcpdl0cA,86448
127
127
  orionis/foundation/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
128
128
  orionis/foundation/config/startup.py,sha256=vbzduprRCNyYeR2nnMaqc1uKXw6PTzAY2jVfXNQKN8I,9691
129
129
  orionis/foundation/config/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -204,7 +204,7 @@ orionis/foundation/config/roots/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm
204
204
  orionis/foundation/config/roots/paths.py,sha256=qQpghBFyF6hdtrt88sLX-4uqrq0frvaQrcDl0fmsTGU,10565
205
205
  orionis/foundation/config/session/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
206
206
  orionis/foundation/config/session/entities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
207
- orionis/foundation/config/session/entities/session.py,sha256=sJoD-_CiwUR88tva-rO22bagG3RTcHXrQGWbq4B5joM,5998
207
+ orionis/foundation/config/session/entities/session.py,sha256=G3dRlzDCsL5_2A2ZKpge2Jwcu6N-YVsum09pWk9oHDc,6085
208
208
  orionis/foundation/config/session/enums/__init__.py,sha256=bvjCUkQbSfKB0J8V1BwOHSjOyZn6xhOmamH3urhoKCA,84
209
209
  orionis/foundation/config/session/enums/same_site_policy.py,sha256=Oo05CJ-5keJWzfflylodyBquofsXqThQPcnar2-ChCw,645
210
210
  orionis/foundation/config/session/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -239,7 +239,7 @@ orionis/foundation/providers/scheduler_provider.py,sha256=72SoixFog9IOE9Ve9Xcfw6
239
239
  orionis/foundation/providers/testing_provider.py,sha256=SrJRpdvcblx9WvX7x9Y3zc7OQfiTf7la0HAJrm2ESlE,3725
240
240
  orionis/foundation/providers/workers_provider.py,sha256=oa_2NIDH6UxZrtuGkkoo_zEoNIMGgJ46vg5CCgAm7wI,3926
241
241
  orionis/metadata/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
242
- orionis/metadata/framework.py,sha256=LNUd5w9wDJVTDwfg_BIGNlRYyyCu3UpQnvpmOQsMlIQ,4109
242
+ orionis/metadata/framework.py,sha256=TVYmpZeUzyz9k7416c1ZPMT-vPrPgTbht4x1iJwTmu4,4109
243
243
  orionis/metadata/package.py,sha256=k7Yriyp5aUcR-iR8SK2ec_lf0_Cyc-C7JczgXa-I67w,16039
244
244
  orionis/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
245
245
  orionis/services/asynchrony/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -292,6 +292,8 @@ orionis/services/introspection/concretes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5
292
292
  orionis/services/introspection/concretes/reflection.py,sha256=5DkNr6gUYdccSo7htzILoV38OAJCwLz-jLXViqZg6eY,55916
293
293
  orionis/services/introspection/concretes/contracts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
294
294
  orionis/services/introspection/concretes/contracts/reflection.py,sha256=LwEAgdN_WLCfS9b8pnFRVfN0PTRK4Br9qngu5km5aIk,24955
295
+ orionis/services/introspection/dataclass/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
296
+ orionis/services/introspection/dataclass/attributes.py,sha256=Z847Gya_0hdiK7eNCI8-tE1o2KtkxSKVcGxXYj94r_A,1186
295
297
  orionis/services/introspection/dependencies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
296
298
  orionis/services/introspection/dependencies/reflection.py,sha256=VEe3cY6LTpOW3dm0IFECTHh7F_9_X-siTAeQc2XRQeM,13451
297
299
  orionis/services/introspection/dependencies/contracts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -415,7 +417,7 @@ orionis/test/validators/web_report.py,sha256=n9BfzOZz6aEiNTypXcwuWbFRG0OdHNSmCNu
415
417
  orionis/test/validators/workers.py,sha256=rWcdRexINNEmGaO7mnc1MKUxkHKxrTsVuHgbnIfJYgc,1206
416
418
  orionis/test/view/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
417
419
  orionis/test/view/render.py,sha256=f-zNhtKSg9R5Njqujbg2l2amAs2-mRVESneLIkWOZjU,4082
418
- orionis-0.522.0.dist-info/licenses/LICENCE,sha256=JhC-z_9mbpUrCfPjcl3DhDA8trNDMzb57cvRSam1avc,1463
420
+ orionis-0.524.0.dist-info/licenses/LICENCE,sha256=JhC-z_9mbpUrCfPjcl3DhDA8trNDMzb57cvRSam1avc,1463
419
421
  tests/container/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
420
422
  tests/container/context/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
421
423
  tests/container/context/test_manager.py,sha256=wOwXpl9rHNfTTexa9GBKYMwK0_-KSQPbI-AEyGNkmAE,1356
@@ -561,8 +563,8 @@ tests/testing/validators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZ
561
563
  tests/testing/validators/test_testing_validators.py,sha256=WPo5GxTP6xE-Dw3X1vZoqOMpb6HhokjNSbgDsDRDvy4,16588
562
564
  tests/testing/view/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
563
565
  tests/testing/view/test_render.py,sha256=tnnMBwS0iKUIbogLvu-7Rii50G6Koddp3XT4wgdFEYM,1050
564
- orionis-0.522.0.dist-info/METADATA,sha256=tcvGNIB23Yo-X3d9ClTaZfP9tChX79uyxXZPstmEyhs,4801
565
- orionis-0.522.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
566
- orionis-0.522.0.dist-info/top_level.txt,sha256=2bdoHgyGZhOtLAXS6Om8OCTmL24dUMC_L1quMe_ETbk,14
567
- orionis-0.522.0.dist-info/zip-safe,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
568
- orionis-0.522.0.dist-info/RECORD,,
566
+ orionis-0.524.0.dist-info/METADATA,sha256=YIVkp5NLpvcgxHQzPTGxREf3MOnCRs65C-f4QkJPAuI,4801
567
+ orionis-0.524.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
568
+ orionis-0.524.0.dist-info/top_level.txt,sha256=2bdoHgyGZhOtLAXS6Om8OCTmL24dUMC_L1quMe_ETbk,14
569
+ orionis-0.524.0.dist-info/zip-safe,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
570
+ orionis-0.524.0.dist-info/RECORD,,