orionis 0.478.0__py3-none-any.whl → 0.480.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,24 +1,26 @@
1
1
  import asyncio
2
2
  import logging
3
- from datetime import datetime
4
- from typing import List, Optional
3
+ from typing import Dict, List, Optional
5
4
  import pytz
5
+ from apscheduler.events import EVENT_JOB_MISSED, EVENT_JOB_ERROR
6
6
  from apscheduler.schedulers.asyncio import AsyncIOScheduler as APSAsyncIOScheduler
7
- from apscheduler.triggers.cron import CronTrigger
8
- from apscheduler.triggers.date import DateTrigger
9
- from apscheduler.triggers.interval import IntervalTrigger
10
- from orionis.app import Orionis
11
7
  from orionis.console.contracts.reactor import IReactor
12
- from orionis.console.enums.task import Task
8
+ from orionis.console.contracts.schedule import ISchedule
13
9
  from orionis.console.exceptions import CLIOrionisRuntimeError
14
- from orionis.console.exceptions.cli_orionis_value_error import CLIOrionisValueError
10
+ from orionis.console.output.contracts.console import IConsole
11
+ from orionis.console.tasks.event import Event
12
+ from orionis.console.tasks.exception_report import ScheduleErrorReporter
13
+ from orionis.foundation.contracts.application import IApplication
15
14
  from orionis.services.log.contracts.log_service import ILogger
16
15
 
17
- class Scheduler():
16
+ class Scheduler(ISchedule):
18
17
 
19
18
  def __init__(
20
19
  self,
21
- reactor: IReactor
20
+ reactor: IReactor,
21
+ app: IApplication,
22
+ console: IConsole,
23
+ error_reporter: ScheduleErrorReporter
22
24
  ) -> None:
23
25
  """
24
26
  Initialize a new instance of the Scheduler class.
@@ -41,7 +43,13 @@ class Scheduler():
41
43
  """
42
44
 
43
45
  # Store the application instance for configuration access.
44
- self.__app = Orionis()
46
+ self.__app = app
47
+
48
+ # Store the console instance for output operations.
49
+ self.__console = console
50
+
51
+ # Store the error reporter instance for handling exceptions.
52
+ self.__error_reporter = error_reporter
45
53
 
46
54
  # Initialize AsyncIOScheduler instance with timezone configuration.
47
55
  self.__scheduler: APSAsyncIOScheduler = APSAsyncIOScheduler(
@@ -50,8 +58,11 @@ class Scheduler():
50
58
 
51
59
  # Clear the APScheduler logger to prevent conflicts with other loggers.
52
60
  # This is necessary to avoid duplicate log messages or conflicts with other logging configurations.
53
- logging.getLogger("apscheduler").handlers.clear()
54
- logging.getLogger("apscheduler").propagate = False
61
+ for name in ["apscheduler", "apscheduler.scheduler", "apscheduler.executors.default"]:
62
+ logger = logging.getLogger(name)
63
+ logger.handlers.clear()
64
+ logger.propagate = False
65
+ logger.disabled = True
55
66
 
56
67
  # Initialize the logger from the application instance.
57
68
  self.__logger: ILogger = self.__app.make('x-orionis.services.log.log_service')
@@ -62,16 +73,57 @@ class Scheduler():
62
73
  # Retrieve and store all available commands from the reactor.
63
74
  self.__available_commands = self.__getCommands()
64
75
 
65
- # Dictionary to hold all scheduled jobs and their details.
66
- self.__jobs: dict = {}
76
+ # Initialize the jobs dictionary to keep track of scheduled jobs.
77
+ self.__events: Dict[str, Event] = {}
67
78
 
68
- # Properties to track the current scheduling context.
69
- self.__command: str = None # The command signature to be scheduled.
70
- self.__args: List[str] = None # Arguments for the command.
71
- self.__purpose: str = None # Purpose or description of the scheduled job.
79
+ # Initialize the jobs list to keep track of all scheduled jobs.
80
+ self.__jobs: List[dict] = []
81
+
82
+ # Add a listener to the scheduler to capture job events such as missed jobs or errors.
83
+ self.__scheduler.add_listener(self.__listener, EVENT_JOB_MISSED | EVENT_JOB_ERROR)
72
84
 
73
85
  # Log the initialization of the Scheduler.
74
- self.__logger.info("Scheduler initialized.")
86
+ self.__logger.info("Orionis scheduler initialized.")
87
+
88
+ def __listener(self, event):
89
+ """
90
+ Handle job events by logging errors and missed jobs.
91
+
92
+ This method acts as a listener for job events emitted by the scheduler. It processes
93
+ two main types of events: job execution errors and missed job executions. When a job
94
+ raises an exception during execution, the method logs the error and delegates reporting
95
+ to the error reporter. If a job is missed (i.e., not executed at its scheduled time),
96
+ the method logs a warning and notifies the error reporter accordingly.
97
+
98
+ Parameters
99
+ ----------
100
+ event : apscheduler.events.JobEvent
101
+ The job event object containing information about the job execution, such as
102
+ job_id, exception, traceback, code, and scheduled_run_time.
103
+
104
+ Returns
105
+ -------
106
+ None
107
+ This method does not return any value. It performs logging and error reporting
108
+ based on the event type.
109
+ """
110
+
111
+ # If the event contains an exception, log the error and report it
112
+ if event.exception:
113
+ self.__logger.error(f"Job {event.job_id} raised an exception: {event.exception}")
114
+ self.__error_reporter.reportException(
115
+ job_id=event.job_id,
116
+ exception=event.exception,
117
+ traceback=event.traceback
118
+ )
119
+
120
+ # If the event indicates a missed job, log a warning and report it
121
+ elif event.code == EVENT_JOB_MISSED:
122
+ self.__logger.warning(f"Job {event.job_id} was missed, scheduled for {event.scheduled_run_time}")
123
+ self.__error_reporter.reportMissed(
124
+ job_id=event.job_id,
125
+ scheduled_time=event.scheduled_run_time
126
+ )
75
127
 
76
128
  def __getCommands(
77
129
  self
@@ -166,56 +218,77 @@ class Scheduler():
166
218
  # Return the description if the command exists, otherwise return None
167
219
  return command_entry['description'] if command_entry else None
168
220
 
169
- def __reset(
221
+ def __loadEvents(
170
222
  self
171
223
  ) -> None:
172
224
  """
173
- Reset the internal state of the Scheduler instance.
225
+ Load all scheduled events from the AsyncIOScheduler into the internal jobs dictionary.
174
226
 
175
- This method clears the current command, arguments, and purpose attributes,
176
- effectively resetting the scheduler's configuration to its initial state.
177
- This can be useful for preparing the scheduler for a new command or job
178
- scheduling without retaining any previous settings.
227
+ This method retrieves all jobs currently managed by the AsyncIOScheduler and populates
228
+ the internal jobs dictionary with their details, including signature, arguments, purpose,
229
+ type, trigger, start date, and end date.
179
230
 
180
231
  Returns
181
232
  -------
182
233
  None
183
- This method does not return any value. It modifies the internal state of the Scheduler.
234
+ This method does not return any value. It updates the internal jobs dictionary.
184
235
  """
185
236
 
186
- self.__command = None
187
- self.__args = None
188
- self.__purpose = None
237
+ # Only load events if the jobs list is empty
238
+ if not self.__jobs:
239
+
240
+ # Iterate through all scheduled jobs in the AsyncIOScheduler
241
+ for signature, event in self.__events.items():
242
+
243
+ # Convert the event to its entity representation
244
+ entity = event.toEntity()
245
+
246
+ # Add the job to the internal jobs list
247
+ self.__jobs.append(entity.toDict())
248
+
249
+ # Create a unique key for the job based on its signature
250
+ self.__scheduler.add_job(
251
+ func= lambda command=signature, args=list(entity.args): self.__reactor.call(
252
+ command,
253
+ args
254
+ ),
255
+ trigger=entity.trigger,
256
+ id=signature,
257
+ name=signature,
258
+ replace_existing=True
259
+ )
189
260
 
190
261
  def command(
191
262
  self,
192
263
  signature: str,
193
264
  args: Optional[List[str]] = None
194
- ) -> 'Scheduler':
265
+ ) -> 'Event':
195
266
  """
196
- Register a command to be scheduled with the specified signature and optional arguments.
267
+ Prepare an Event instance for a given command signature and its arguments.
197
268
 
198
- This method validates the provided command signature and arguments, checks if the command
199
- is available in the list of registered commands, and stores the command details internally
200
- for scheduling. The command's description is also retrieved and stored for reference.
269
+ This method validates the provided command signature and arguments, ensuring
270
+ that the command exists among the registered commands and that the arguments
271
+ are in the correct format. If validation passes, it creates and returns an
272
+ Event object representing the scheduled command, including its signature,
273
+ arguments, and description.
201
274
 
202
275
  Parameters
203
276
  ----------
204
277
  signature : str
205
278
  The unique signature identifying the command to be scheduled. Must be a non-empty string.
206
279
  args : Optional[List[str]], optional
207
- A list of string arguments to be passed to the command. If not provided, an empty list is used.
280
+ A list of string arguments to be passed to the command. Defaults to None.
208
281
 
209
282
  Returns
210
283
  -------
211
- Scheduler
212
- Returns the current instance of the Scheduler to allow method chaining.
284
+ Event
285
+ An Event instance containing the command signature, arguments, and its description.
213
286
 
214
287
  Raises
215
288
  ------
216
289
  ValueError
217
- If the signature is not a non-empty string, if the arguments are not a list or None,
218
- or if the command signature is not available among registered commands.
290
+ If the command signature is not a non-empty string, if the arguments are not a list
291
+ of strings or None, or if the command does not exist among the registered commands.
219
292
  """
220
293
 
221
294
  # Validate that the command signature is a non-empty string
@@ -230,215 +303,15 @@ class Scheduler():
230
303
  if not self.__isAvailable(signature):
231
304
  raise ValueError(f"The command '{signature}' is not available or does not exist.")
232
305
 
233
- # Store the command signature
234
- self.__command = signature
235
-
236
- # If purpose is not already set, retrieve and set the command's description
237
- if self.__purpose is None:
238
- self.__purpose = self.__getDescription(signature)
239
-
240
- # Store the provided arguments or default to an empty list
241
- self.__args = args if args is not None else []
242
-
243
- # Return self to support method chaining
244
- return self
245
-
246
- def purpose(
247
- self,
248
- purpose: str
249
- ) -> 'Scheduler':
250
- """
251
- Set the purpose or description for the scheduled command.
252
-
253
- This method assigns a human-readable purpose or description to the command
254
- that is being scheduled. The purpose must be a non-empty string. This can
255
- be useful for documentation, logging, or displaying information about the
256
- scheduled job.
257
-
258
- Parameters
259
- ----------
260
- purpose : str
261
- The purpose or description to associate with the scheduled command.
262
- Must be a non-empty string.
263
-
264
- Returns
265
- -------
266
- Scheduler
267
- Returns the current instance of the Scheduler to allow method chaining.
268
-
269
- Raises
270
- ------
271
- ValueError
272
- If the provided purpose is not a non-empty string.
273
- """
274
-
275
- # Validate that the purpose is a non-empty string
276
- if not isinstance(purpose, str) or not purpose.strip():
277
- raise ValueError("The purpose must be a non-empty string.")
278
-
279
- # Set the internal purpose attribute
280
- self.__purpose = purpose
281
-
282
- # Return self to support method chaining
283
- return self
284
-
285
- def onceAt(
286
- self,
287
- date: datetime
288
- ) -> bool:
289
- """
290
- Schedule a command to run once at a specific date and time.
291
-
292
- This method schedules the currently registered command to execute exactly once at the
293
- specified datetime using the AsyncIOScheduler. The job is registered internally and
294
- added to the scheduler instance.
295
-
296
- Parameters
297
- ----------
298
- date : datetime.datetime
299
- The date and time at which the command should be executed. Must be a valid `datetime` instance.
300
-
301
- Returns
302
- -------
303
- bool
304
- Returns True if the job was successfully scheduled.
305
-
306
- Raises
307
- ------
308
- CLIOrionisRuntimeError
309
- If the provided date is not a `datetime` instance or if there is an error while scheduling the job.
310
- """
311
-
312
- try:
313
-
314
- # Ensure the provided date is a valid datetime instance.
315
- if not isinstance(date, datetime):
316
- raise CLIOrionisRuntimeError(
317
- "The date must be an instance of datetime."
318
- )
319
-
320
- # Register the job details internally.
321
- self.__jobs[self.__command] = Task(
322
- signature=self.__command,
323
- args=self.__args,
324
- purpose=self.__purpose,
325
- trigger='once_at',
326
- details=f"Date: {date.strftime('%Y-%m-%d %H:%M:%S')}, Timezone: {str(date.tzinfo) if date.tzinfo else 'UTC'}"
327
- )
328
-
329
- # Add the job to the scheduler.
330
- self.__scheduler.add_job(
331
- func= lambda command=self.__command, args=list(self.__args): self.__reactor.call(
332
- command,
333
- args
334
- ),
335
- trigger=DateTrigger(
336
- run_date=date
337
- ),
338
- id=self.__command,
339
- name=self.__command,
340
- replace_existing=True
341
- )
342
-
343
- # Log the scheduling of the command.
344
- self.__logger.info(
345
- f"Scheduled command '{self.__command}' to run once at {date.strftime('%Y-%m-%d %H:%M:%S')}"
346
- )
347
-
348
- # Reset the internal state for future scheduling.
349
- self.__reset()
350
-
351
- # Return True to indicate successful scheduling.
352
- return True
353
-
354
- except Exception as e:
355
-
356
- # Reraise known CLIOrionisRuntimeError exceptions.
357
- if isinstance(e, CLIOrionisRuntimeError):
358
- raise e
359
-
360
- # Wrap and raise any other exceptions as CLIOrionisRuntimeError.
361
- raise CLIOrionisRuntimeError(f"Error scheduling the job: {str(e)}")
362
-
363
- def everySeconds(
364
- self,
365
- seconds: int,
366
- start_date: Optional[datetime] = None,
367
- end_date: Optional[datetime] = None
368
- ) -> 'Scheduler':
369
- """
370
- Schedule a command to run at regular intervals in seconds.
371
-
372
- This method sets the interval for the currently registered command to execute
373
- every specified number of seconds. The job is registered internally and added
374
- to the scheduler instance.
375
-
376
- Parameters
377
- ----------
378
- seconds : int
379
- The interval in seconds at which the command should be executed. Must be a positive integer.
380
-
381
- Returns
382
- -------
383
- Scheduler
384
- Returns the current instance of the Scheduler to allow method chaining.
385
-
386
- Raises
387
- ------
388
- ValueError
389
- If the provided seconds is not a positive integer.
390
- """
391
-
392
- try:
393
-
394
- # Validate that the seconds parameter is a positive integer.
395
- if not isinstance(seconds, int) or seconds <= 0:
396
- raise CLIOrionisValueError("The interval must be a positive integer.")
397
-
398
- # If start_date is provided, ensure it is a datetime instance.
399
- if start_date is not None and not isinstance(start_date, datetime):
400
- raise CLIOrionisValueError("Start date must be a datetime instance.")
401
-
402
- # If end_date is provided, ensure it is a datetime instance.
403
- if end_date is not None and not isinstance(end_date, datetime):
404
- raise CLIOrionisValueError("End date must be a datetime instance.")
405
-
406
- # Store the interval in the jobs dictionary.
407
- self.__jobs[self.__command] = Task(
408
- signature=self.__command,
409
- args=self.__args,
410
- purpose=self.__purpose,
411
- trigger='every_seconds',
412
- details=f"Interval: {seconds} seconds, Start Date: {start_date.strftime('%Y-%m-%d %H:%M:%S') if start_date else 'None'}, End Date: {end_date.strftime('%Y-%m-%d %H:%M:%S') if end_date else 'None'}"
413
- )
414
-
415
- # Add the job to the scheduler with an interval trigger.
416
- self.__scheduler.add_job(
417
- func=lambda command=self.__command, args=list(self.__args): self.__reactor.call(
418
- command,
419
- args
420
- ),
421
- trigger=IntervalTrigger(
422
- seconds=seconds,
423
- start_date=start_date,
424
- end_date=end_date
425
- ),
426
- id=self.__command,
427
- name=self.__command,
428
- replace_existing=True
429
- )
430
-
431
- # Return self to support method chaining.
432
- return self
433
-
434
- except Exception as e:
435
-
436
- # Reraise known CLIOrionisValueError exceptions.
437
- if isinstance(e, CLIOrionisValueError):
438
- raise e
306
+ # Store the command and its arguments for scheduling
307
+ self.__events[signature] = Event(
308
+ signature=signature,
309
+ args=args or [],
310
+ purpose=self.__getDescription(signature)
311
+ )
439
312
 
440
- # Wrap and raise any other exceptions as CLIOrionisRuntimeError.
441
- raise CLIOrionisRuntimeError(f"Error scheduling the job: {str(e)}")
313
+ # Return the Event instance for further scheduling configuration
314
+ return self.__events[signature]
442
315
 
443
316
  async def start(self) -> None:
444
317
  """
@@ -457,6 +330,9 @@ class Scheduler():
457
330
  # Start the AsyncIOScheduler to handle asynchronous jobs.
458
331
  try:
459
332
 
333
+ # Load existing events into the scheduler
334
+ self.events()
335
+
460
336
  # Ensure we're in an asyncio context
461
337
  asyncio.get_running_loop()
462
338
 
@@ -467,14 +343,10 @@ class Scheduler():
467
343
 
468
344
  # Keep the event loop alive to process scheduled jobs
469
345
  try:
470
-
471
- # Wait for the scheduler to start and keep it running
472
- while True:
346
+ while self.__scheduler.running and self.__scheduler.get_jobs():
473
347
  await asyncio.sleep(1)
474
-
475
- except KeyboardInterrupt:
476
-
477
- # Handle graceful shutdown on keyboard interrupt
348
+ except (KeyboardInterrupt, asyncio.CancelledError):
349
+ # Handle graceful shutdown on keyboard interrupt or cancellation
478
350
  await self.shutdown()
479
351
 
480
352
  except Exception as e:
@@ -577,22 +449,46 @@ class Scheduler():
577
449
  # Job not found or other error
578
450
  return False
579
451
 
580
- def jobs(self) -> dict:
452
+ def events(self) -> list:
581
453
  """
582
454
  Retrieve all scheduled jobs currently managed by the Scheduler.
583
455
 
584
- This method returns a dictionary containing information about all jobs that have been
585
- registered and scheduled through this Scheduler instance. Each entry in the dictionary
586
- represents a scheduled job, where the key is the command signature and the value is a
587
- dictionary with details such as the signature, arguments, purpose, type, trigger, start time,
588
- and end time.
456
+ This method loads and returns a list of dictionaries, each representing a scheduled job
457
+ managed by this Scheduler instance. Each dictionary contains details such as the command
458
+ signature, arguments, purpose, random delay, start and end dates, and additional job details.
589
459
 
590
460
  Returns
591
461
  -------
592
- dict
593
- A dictionary mapping command signatures to their corresponding job details. Each value
594
- is a dictionary containing information about the scheduled job.
462
+ list of dict
463
+ A list where each element is a dictionary containing information about a scheduled job.
464
+ Each dictionary includes the following keys:
465
+ - 'signature': str, the command signature.
466
+ - 'args': list, the arguments passed to the command.
467
+ - 'purpose': str, the description or purpose of the job.
468
+ - 'random_delay': any, the random delay associated with the job (if any).
469
+ - 'start_date': str or None, the formatted start date and time of the job, or None if not set.
470
+ - 'end_date': str or None, the formatted end date and time of the job, or None if not set.
471
+ - 'details': any, additional details about the job.
595
472
  """
596
473
 
597
- # Return the internal dictionary holding all scheduled jobs and their details.
598
- return self.__jobs
474
+ # Ensure all events are loaded into the internal jobs list
475
+ self.__loadEvents()
476
+
477
+ # Initialize a list to hold details of each scheduled job
478
+ events: list = []
479
+
480
+ # Iterate over each job in the internal jobs list
481
+ for job in self.__jobs:
482
+ # Append a dictionary with relevant job details to the events list
483
+ events.append({
484
+ 'signature': job['signature'],
485
+ 'args': job['args'],
486
+ 'purpose': job['purpose'],
487
+ 'random_delay': job['random_delay'],
488
+ 'start_date': job['start_date'].strftime('%Y-%m-%d %H:%M:%S') if job['start_date'] else None,
489
+ 'end_date': job['end_date'].strftime('%Y-%m-%d %H:%M:%S') if job['end_date'] else None,
490
+ 'details': job['details']
491
+ })
492
+
493
+ # Return the list of scheduled job details
494
+ return events
@@ -511,35 +511,46 @@ class Container(IContainer):
511
511
  registered in the container under both the abstract and the alias.
512
512
  """
513
513
 
514
- # Ensure that the abstract is an abstract class
515
- IsAbstractClass(abstract, f"Instance {Lifetime.SINGLETON}")
514
+ # Validate the enforce_decoupling parameter
515
+ if isinstance(enforce_decoupling, bool):
516
516
 
517
- # Ensure that the instance is a valid instance
518
- IsInstance(instance)
517
+ # Ensure that the abstract is an abstract class
518
+ IsAbstractClass(abstract, f"Instance {Lifetime.SINGLETON}")
519
519
 
520
- # Ensure that instance is NOT a subclass of abstract
521
- if enforce_decoupling:
522
- IsNotSubclass(abstract, instance.__class__)
520
+ # Ensure that the instance is a valid instance
521
+ IsInstance(instance)
523
522
 
524
- # Validate that instance is a subclass of abstract
525
- else:
526
- IsSubclass(abstract, instance.__class__)
523
+ # Ensure that instance is NOT a subclass of abstract
524
+ if enforce_decoupling:
525
+ IsNotSubclass(abstract, instance.__class__)
527
526
 
528
- # Ensure implementation
529
- ImplementsAbstractMethods(
530
- abstract=abstract,
531
- instance=instance
532
- )
527
+ # Validate that instance is a subclass of abstract
528
+ else:
529
+ IsSubclass(abstract, instance.__class__)
530
+
531
+ # Ensure implementation
532
+ ImplementsAbstractMethods(
533
+ abstract=abstract,
534
+ instance=instance
535
+ )
536
+
537
+ # Ensure that the alias is a valid string if provided
538
+ if alias:
539
+ IsValidAlias(alias)
540
+ else:
541
+ rf_asbtract = ReflectionAbstract(abstract)
542
+ alias = rf_asbtract.getModuleWithClassName()
543
+
544
+ # If the service is already registered, drop it
545
+ self.drop(abstract, alias)
533
546
 
534
- # Ensure that the alias is a valid string if provided
535
- if alias:
536
- IsValidAlias(alias)
537
547
  else:
538
- rf_asbtract = ReflectionAbstract(abstract)
539
- alias = rf_asbtract.getModuleWithClassName()
540
548
 
541
- # If the service is already registered, drop it
542
- self.drop(abstract, alias)
549
+ # Drop the existing alias if it exists
550
+ self.drop(alias=alias)
551
+
552
+ # If enforce_decoupling is not a boolean, set it to False
553
+ enforce_decoupling = False
543
554
 
544
555
  # Register the instance with the abstract type
545
556
  self.__bindings[abstract] = Binding(
@@ -1,6 +1,7 @@
1
1
  from typing import Any
2
+ from orionis.app import Orionis
2
3
  from orionis.container.exceptions import OrionisContainerAttributeError, OrionisContainerException
3
- from orionis.foundation.application import Application, IApplication
4
+ from orionis.foundation.application import IApplication
4
5
  from typing import TypeVar, Any
5
6
 
6
7
  T = TypeVar('T')
@@ -34,7 +35,7 @@ class FacadeMeta(type):
34
35
  class Facade(metaclass=FacadeMeta):
35
36
 
36
37
  # Application instance to resolve services
37
- _app: IApplication = Application()
38
+ _app: IApplication = Orionis()
38
39
 
39
40
  @classmethod
40
41
  def getFacadeAccessor(cls) -> str:
@@ -181,6 +181,7 @@ class Application(Container, IApplication):
181
181
  from orionis.foundation.providers.executor_provider import ConsoleExecuteProvider
182
182
  from orionis.foundation.providers.reactor_provider import ReactorProvider
183
183
  from orionis.foundation.providers.performance_counter_provider import PerformanceCounterProvider
184
+ from orionis.foundation.providers.scheduler_provider import ScheduleProvider
184
185
 
185
186
  # Core framework providers
186
187
  core_providers = [
@@ -193,7 +194,8 @@ class Application(Container, IApplication):
193
194
  InspirationalProvider,
194
195
  ConsoleExecuteProvider,
195
196
  ReactorProvider,
196
- PerformanceCounterProvider
197
+ PerformanceCounterProvider,
198
+ ScheduleProvider
197
199
  ]
198
200
 
199
201
  # Register each core provider
@@ -1812,7 +1814,7 @@ class Application(Container, IApplication):
1812
1814
  self,
1813
1815
  key: str = None,
1814
1816
  default: Any = None
1815
- ) -> Any:
1817
+ ) -> str:
1816
1818
  """
1817
1819
  Retrieve application path configuration values using dot notation.
1818
1820
 
@@ -1832,7 +1834,7 @@ class Application(Container, IApplication):
1832
1834
 
1833
1835
  Returns
1834
1836
  -------
1835
- Any
1837
+ str
1836
1838
  The path configuration value corresponding to the given key, the entire
1837
1839
  paths dictionary if key is None, or the default value if the key is
1838
1840
  not found.
@@ -1923,6 +1925,9 @@ class Application(Container, IApplication):
1923
1925
  # Check if already booted
1924
1926
  if not self.__booted:
1925
1927
 
1928
+ # Register the application instance in the container
1929
+ self.instance(IApplication, self, alias="x-orionis.foundation.application", enforce_decoupling='X-ORIONIS')
1930
+
1926
1931
  # Load configuration if not already set
1927
1932
  self.__loadConfig()
1928
1933