orionis 0.535.0__py3-none-any.whl → 0.537.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.
@@ -5,24 +5,32 @@ from orionis.console.enums.event import Event as EventEntity
5
5
 
6
6
  class IEvent(ABC):
7
7
 
8
- @abstractmethod
9
- def toEntity(
10
- self
11
- ) -> EventEntity:
8
+ def misfireGraceTime(
9
+ self,
10
+ seconds: int = 60
11
+ ) -> 'IEvent':
12
12
  """
13
- Retrieve the event details as an EventEntity instance.
13
+ Set the misfire grace time for the event.
14
14
 
15
- This method gathers all relevant attributes of the current Event object,
16
- such as its signature, arguments, purpose, random delay, start and end dates,
17
- and trigger, and returns them encapsulated in an EventEntity object.
15
+ This method allows you to specify a grace period (in seconds) during which
16
+ a missed execution of the event can still be executed. If the event is not
17
+ executed within this time frame after its scheduled time, it will be skipped.
18
+
19
+ Parameters
20
+ ----------
21
+ seconds : int
22
+ The number of seconds to allow for a misfire grace period. Must be a positive integer.
18
23
 
19
24
  Returns
20
25
  -------
21
- EventEntity
22
- An EventEntity instance containing the event's signature, arguments,
23
- purpose, random delay, start date, end date, and trigger.
26
+ Event
27
+ Returns the current instance of the Event to allow method chaining.
28
+
29
+ Raises
30
+ ------
31
+ CLIOrionisValueError
32
+ If the provided seconds is not a positive integer.
24
33
  """
25
- pass
26
34
 
27
35
  @abstractmethod
28
36
  def purpose(
@@ -60,4 +60,10 @@ class Event:
60
60
  details: Optional[str] = None
61
61
 
62
62
  # Optional listener that implements IScheduleEventListener
63
- listener: Optional[IScheduleEventListener] = None
63
+ listener: Optional[IScheduleEventListener] = None
64
+
65
+ # Maximum number of concurrent instances allowed for the event
66
+ max_instances: int = 1
67
+
68
+ # Grace time in seconds for misfired events
69
+ misfire_grace_time : Optional[int] = None
@@ -77,6 +77,9 @@ class Event(IEvent):
77
77
  # Initialize the maximum instances attribute as None
78
78
  self.__max_instances: Optional[int] = None
79
79
 
80
+ # Initialize the misfire grace time attribute as None
81
+ self.__misfire_grace_time: Optional[int] = None
82
+
80
83
  def toEntity(
81
84
  self
82
85
  ) -> EventEntity:
@@ -128,6 +131,14 @@ class Event(IEvent):
128
131
  if self.__listener is not None and not issubclass(self.__listener, IScheduleEventListener):
129
132
  raise CLIOrionisValueError("Listener must implement IScheduleEventListener interface or be None.")
130
133
 
134
+ # Validate that max_instances is a positive integer if it is set
135
+ if self.__max_instances is not None and (not isinstance(self.__max_instances, int) or self.__max_instances <= 0):
136
+ raise CLIOrionisValueError("Max instances must be a positive integer or None.")
137
+
138
+ # Validate that misfire_grace_time is a positive integer if it is set
139
+ if self.__misfire_grace_time is not None and (not isinstance(self.__misfire_grace_time, int) or self.__misfire_grace_time <= 0):
140
+ raise CLIOrionisValueError("Misfire grace time must be a positive integer or None.")
141
+
131
142
  # Construct and return an EventEntity with the current event's attributes
132
143
  return EventEntity(
133
144
  signature=self.__signature,
@@ -138,9 +149,48 @@ class Event(IEvent):
138
149
  end_date=self.__end_date,
139
150
  trigger=self.__trigger,
140
151
  details=self.__details,
141
- listener=self.__listener
152
+ listener=self.__listener,
153
+ max_instances=self.__max_instances,
154
+ misfire_grace_time=self.__misfire_grace_time
142
155
  )
143
156
 
157
+ def misfireGraceTime(
158
+ self,
159
+ seconds: int = 60
160
+ ) -> 'Event':
161
+ """
162
+ Set the misfire grace time for the event.
163
+
164
+ This method allows you to specify a grace period (in seconds) during which
165
+ a missed execution of the event can still be executed. If the event is not
166
+ executed within this time frame after its scheduled time, it will be skipped.
167
+
168
+ Parameters
169
+ ----------
170
+ seconds : int
171
+ The number of seconds to allow for a misfire grace period. Must be a positive integer.
172
+
173
+ Returns
174
+ -------
175
+ Event
176
+ Returns the current instance of the Event to allow method chaining.
177
+
178
+ Raises
179
+ ------
180
+ CLIOrionisValueError
181
+ If the provided seconds is not a positive integer.
182
+ """
183
+
184
+ # Validate that the seconds parameter is a positive integer
185
+ if not isinstance(seconds, int) or seconds <= 0:
186
+ raise CLIOrionisValueError("Misfire grace time must be a positive integer.")
187
+
188
+ # Set the internal misfire grace time attribute
189
+ self.__misfire_grace_time = seconds
190
+
191
+ # Return self to support method chaining
192
+ return self
193
+
144
194
  def purpose(
145
195
  self,
146
196
  purpose: str
@@ -482,8 +482,8 @@ class Schedule(ISchedule):
482
482
  # Invoke the listener, handling both coroutine and regular functions
483
483
  try:
484
484
 
485
- # Use Coroutine to handle both sync and async listeners
486
- Coroutine(listener).invoke(event_data, self)
485
+ # Execute coroutine listeners using container's invoke method
486
+ self.__app.invoke(listener, event_data, self)
487
487
 
488
488
  except BaseException as e:
489
489
 
@@ -550,26 +550,13 @@ class Schedule(ISchedule):
550
550
 
551
551
  # Initialize the listener if it's a class
552
552
  if isinstance(listener, type):
553
- listener = listener()
553
+ listener = self.__app.make(listener)
554
554
 
555
555
  # Check if the listener has a method corresponding to the event type
556
556
  if hasattr(listener, scheduler_event) and callable(getattr(listener, scheduler_event)):
557
557
 
558
- # Retrieve the method from the listener
559
- listener_method = getattr(listener, scheduler_event)
560
-
561
558
  # Invoke the listener method, handling both coroutine and regular functions
562
- try:
563
-
564
- # Use Coroutine to handle both sync and async listener methods
565
- Coroutine(listener_method).invoke(event_data, self)
566
-
567
- except Exception as e:
568
-
569
- # Construct and log error message
570
- error_msg = f"An error occurred while invoking the listener_method for event '{scheduler_event}': {str(e)}"
571
- self.__logger.error(error_msg)
572
- raise CLIOrionisRuntimeError(error_msg) from e
559
+ self.__app.call(listener, scheduler_event, event_data, self)
573
560
 
574
561
  else:
575
562
 
@@ -707,7 +694,7 @@ class Schedule(ISchedule):
707
694
  and listener invocation for the job error event.
708
695
  """
709
696
  # Log an error message indicating that the job raised an exception
710
- self.__logger.error(f"Task {event.job_id} raised an exception: {event.exception}")
697
+ self.__logger.error(f"Task '{event.job_id}' raised an exception: {event.exception}")
711
698
 
712
699
  # If a listener is registered for this job ID, invoke the listener with the event details
713
700
  job_event_data = self.__getTaskFromSchedulerById(event.job_id)
@@ -758,7 +745,7 @@ class Schedule(ISchedule):
758
745
  """
759
746
 
760
747
  # Log an informational message indicating that the job has been submitted
761
- self.__logger.info(f"Task {event.job_id} submitted to executor.")
748
+ self.__logger.info(f"Task '{event.job_id}' submitted to executor.")
762
749
 
763
750
  # Create entity for job submitted event
764
751
  data_event = self.__getTaskFromSchedulerById(event.job_id, event.code)
@@ -793,7 +780,7 @@ class Schedule(ISchedule):
793
780
  """
794
781
 
795
782
  # Log an informational message indicating that the job has been executed
796
- self.__logger.info(f"Task {event.job_id} executed.")
783
+ self.__logger.info(f"Task '{event.job_id}' executed.")
797
784
 
798
785
  # Create entity for job executed event
799
786
  data_event = self.__getTaskFromSchedulerById(event.job_id, event.code)
@@ -828,7 +815,7 @@ class Schedule(ISchedule):
828
815
  """
829
816
 
830
817
  # Log a warning indicating that the job was missed
831
- self.__logger.warning(f"Task {event.job_id} was missed. It was scheduled to run at {event.scheduled_run_time}.")
818
+ self.__logger.warning(f"Task '{event.job_id}' was missed. It was scheduled to run at {event.scheduled_run_time}.")
832
819
 
833
820
  # Create entity for job missed event
834
821
  data_event = self.__getTaskFromSchedulerById(event.job_id, event.code)
@@ -863,7 +850,7 @@ class Schedule(ISchedule):
863
850
  """
864
851
 
865
852
  # Log an error message indicating that the job exceeded maximum instances
866
- self.__logger.error(f"Task {event.job_id} exceeded maximum instances")
853
+ self.__logger.error(f"Task '{event.job_id}' exceeded maximum instances")
867
854
 
868
855
  # Create entity for job max instances event
869
856
  data_event = self.__getTaskFromSchedulerById(event.job_id, event.code)
@@ -897,7 +884,7 @@ class Schedule(ISchedule):
897
884
  """
898
885
 
899
886
  # Log the removal of the job
900
- self.__logger.info(f"Task {event.job_id} has been removed.")
887
+ self.__logger.info(f"Task '{event.job_id}' has been removed.")
901
888
 
902
889
  # Create entity for job removed event
903
890
  data_event = self.__getTaskFromSchedulerById(event.job_id, event.code)
@@ -944,7 +931,9 @@ class Schedule(ISchedule):
944
931
  trigger=entity.trigger,
945
932
  id=signature,
946
933
  name=signature,
947
- replace_existing=True
934
+ replace_existing=True,
935
+ max_instances=entity.max_instances,
936
+ misfire_grace_time=entity.misfire_grace_time
948
937
  )
949
938
 
950
939
  # If a listener is associated with the event, register it
@@ -17,6 +17,8 @@ from orionis.services.introspection.callables.reflection import ReflectionCallab
17
17
  from orionis.services.introspection.concretes.reflection import ReflectionConcrete
18
18
  from orionis.services.introspection.dependencies.entities.argument import Argument
19
19
  from orionis.services.introspection.dependencies.entities.resolve_argument import ResolveArguments
20
+ from orionis.services.introspection.dependencies.reflection import ReflectDependencies
21
+ from orionis.services.introspection.instances.reflection import ReflectionInstance
20
22
 
21
23
  class Container(IContainer):
22
24
 
@@ -206,9 +208,72 @@ class Container(IContainer):
206
208
 
207
209
  try:
208
210
 
209
- # Check if the callable is a coroutine function
210
- result = fn(*args, **kwargs)
211
- return self.__handleSyncAsyncResult(result)
211
+ # Count the total number of provided arguments
212
+ total_provided_args = len(args) + len(kwargs)
213
+
214
+ # Inspect the callable to determine its signature and parameters
215
+ dependencies = ReflectDependencies(fn).getCallableDependencies()
216
+ total_dependencies = len(dependencies.resolved) + len(dependencies.unresolved)
217
+
218
+ # If the callable does not require any dependencies, invoke directly
219
+ if total_dependencies == 0:
220
+ result = fn(*args, **kwargs)
221
+ return self.__handleSyncAsyncResult(result)
222
+
223
+ # If enough arguments are provided, invoke directly
224
+ if total_provided_args >= total_dependencies:
225
+ result = fn(*args, **kwargs)
226
+ return self.__handleSyncAsyncResult(result)
227
+
228
+ # If not enough arguments are provided, attempt to resolve missing dependencies
229
+ if total_provided_args < total_dependencies:
230
+
231
+ # New lists to hold the final arguments to pass to the callable
232
+ n_args = []
233
+ n_kwargs = {}
234
+
235
+ # Iterate through the function's required arguments in order
236
+ args_index = 0
237
+
238
+ # Iterate over all dependencies in the order they were defined
239
+ for name, dep in dependencies.ordered.items():
240
+
241
+ # Check if the argument was provided positionally and hasn't been used yet
242
+ if args_index < len(args) and name not in kwargs:
243
+
244
+ # Add the positional argument to the new list
245
+ n_args.append(args[args_index])
246
+
247
+ # Move to the next positional argument
248
+ args_index += 1
249
+
250
+ # Check if the argument was provided as a keyword argument
251
+ elif name in kwargs:
252
+
253
+ # Add the keyword argument to the new dictionary
254
+ n_kwargs[name] = kwargs[name]
255
+
256
+ # Remove the argument from the original kwargs to avoid duplication
257
+ del kwargs[name]
258
+
259
+ # If not provided, attempt to resolve it from the container
260
+ else:
261
+
262
+ n_kwargs[name] = self.__resolveSingleDependency(
263
+ getattr(fn, '__name__', str(fn)),
264
+ name,
265
+ dep
266
+ )
267
+
268
+ # Add any remaining positional arguments that weren't mapped to specific parameters
269
+ n_args.extend(args[args_index:])
270
+
271
+ # Add any remaining keyword arguments that weren't processed
272
+ n_kwargs.update(kwargs)
273
+
274
+ # Invoke the function with the resolved arguments
275
+ result = fn(*n_args, **n_kwargs)
276
+ return self.__handleSyncAsyncResult(result)
212
277
 
213
278
  except TypeError as e:
214
279
 
@@ -219,8 +284,9 @@ class Container(IContainer):
219
284
 
220
285
  # Raise a more informative exception with the function name and signature
221
286
  raise OrionisContainerException(
222
- f"Failed to invoke function [{function_name}] with the provided arguments: {e}\n"
223
- f"Expected function signature: [{signature}]"
287
+ f"Failed to invoke function [{function_name}] with the provided arguments: {e}. "
288
+ f"Note that this may include a reference to the same 'self' object.\n"
289
+ f"Expected function signature: {function_name}{signature}"
224
290
  ) from e
225
291
 
226
292
  def transient(
@@ -2360,6 +2426,79 @@ class Container(IContainer):
2360
2426
  f"Method '{method_name}' not found or not callable on instance '{type(instance).__name__}'."
2361
2427
  )
2362
2428
 
2429
+ def invoke(
2430
+ self,
2431
+ fn: Callable,
2432
+ *args,
2433
+ **kwargs
2434
+ ) -> Any:
2435
+ """
2436
+ Invokes a callable with automatic dependency injection and sync/async handling.
2437
+
2438
+ Parameters
2439
+ ----------
2440
+ fn : Callable
2441
+ The callable to invoke.
2442
+ *args : tuple
2443
+ Positional arguments to pass to the callable.
2444
+ **kwargs : dict
2445
+ Keyword arguments to pass to the callable.
2446
+
2447
+ Returns
2448
+ -------
2449
+ Any
2450
+ The result of the callable invocation.
2451
+ """
2452
+
2453
+ # Validate that fn is indeed callable
2454
+ if not callable(fn):
2455
+ raise OrionisContainerException(
2456
+ f"Provided fn '{getattr(fn, '__name__', str(fn))}' is not callable."
2457
+ )
2458
+
2459
+ # Execute the callable with appropriate handling
2460
+ return self.__executeMethod(fn, *args, **kwargs)
2461
+
2462
+ async def invokeAsync(
2463
+ self,
2464
+ fn: Callable,
2465
+ *args,
2466
+ **kwargs
2467
+ ) -> Any:
2468
+ """
2469
+ Async version of invoke for when you're in an async context and need to await the result.
2470
+
2471
+ Parameters
2472
+ ----------
2473
+ fn : Callable
2474
+ The callable to invoke.
2475
+ *args : tuple
2476
+ Positional arguments to pass to the callable.
2477
+ **kwargs : dict
2478
+ Keyword arguments to pass to the callable.
2479
+
2480
+ Returns
2481
+ -------
2482
+ Any
2483
+ The result of the callable invocation, properly awaited if async.
2484
+ """
2485
+
2486
+ # Validate that fn is indeed callable
2487
+ if not callable(fn):
2488
+ raise OrionisContainerException(
2489
+ f"Provided fn '{getattr(fn, '__name__', str(fn))}' is not callable."
2490
+ )
2491
+
2492
+ # Execute the callable with appropriate handling
2493
+ result = self.__executeMethod(fn, *args, **kwargs)
2494
+
2495
+ # If the result is a coroutine, await it
2496
+ if asyncio.iscoroutine(result):
2497
+ return await result
2498
+
2499
+ # Otherwise, return the result directly
2500
+ return result
2501
+
2363
2502
  def __executeMethod(
2364
2503
  self,
2365
2504
  method: Callable,
@@ -449,3 +449,55 @@ class IContainer(ABC):
449
449
  The result of the method call, properly awaited if async.
450
450
  """
451
451
  pass
452
+
453
+ @abstractmethod
454
+ def invoke(
455
+ self,
456
+ fn: Callable,
457
+ *args,
458
+ **kwargs
459
+ ) -> Any:
460
+ """
461
+ Invokes a callable with automatic dependency injection and sync/async handling.
462
+
463
+ Parameters
464
+ ----------
465
+ fn : Callable
466
+ The callable to invoke.
467
+ *args : tuple
468
+ Positional arguments to pass to the callable.
469
+ **kwargs : dict
470
+ Keyword arguments to pass to the callable.
471
+
472
+ Returns
473
+ -------
474
+ Any
475
+ The result of the callable invocation.
476
+ """
477
+ pass
478
+
479
+ @abstractmethod
480
+ async def invokeAsync(
481
+ self,
482
+ fn: Callable,
483
+ *args,
484
+ **kwargs
485
+ ) -> Any:
486
+ """
487
+ Async version of invoke for when you're in an async context and need to await the result.
488
+
489
+ Parameters
490
+ ----------
491
+ fn : Callable
492
+ The callable to invoke.
493
+ *args : tuple
494
+ Positional arguments to pass to the callable.
495
+ **kwargs : dict
496
+ Keyword arguments to pass to the callable.
497
+
498
+ Returns
499
+ -------
500
+ Any
501
+ The result of the callable invocation, properly awaited if async.
502
+ """
503
+ pass
@@ -13,7 +13,7 @@ class BaseExceptionHandler(IBaseExceptionHandler):
13
13
  # Example: OrionisContainerException
14
14
  ]
15
15
 
16
- async def destructureException(self, e: BaseException) -> Throwable:
16
+ async def destructureException(self, exception: BaseException) -> Throwable:
17
17
  """
18
18
  Converts an exception into a structured `Throwable` object containing detailed information.
19
19
 
@@ -35,13 +35,13 @@ class BaseExceptionHandler(IBaseExceptionHandler):
35
35
 
36
36
  # Create and return a Throwable object with detailed exception information
37
37
  return Throwable(
38
- classtype=type(e), # The class/type of the exception
39
- message=str(e), # The exception message as a string
40
- args=e.args, # The arguments passed to the exception
41
- traceback=getattr(e, '__traceback__', None) # The traceback object, if available
38
+ classtype=type(exception), # The class/type of the exception
39
+ message=str(exception), # The exception message as a string
40
+ args=exception.args, # The arguments passed to the exception
41
+ traceback=getattr(exception, '__traceback__', None) # The traceback object, if available
42
42
  )
43
43
 
44
- async def shouldIgnoreException(self, e: BaseException) -> bool:
44
+ async def shouldIgnoreException(self, exception: BaseException) -> bool:
45
45
  """
46
46
  Determines if the exception should be ignored (not handled) by the handler.
47
47
 
@@ -57,11 +57,11 @@ class BaseExceptionHandler(IBaseExceptionHandler):
57
57
  """
58
58
 
59
59
  # Ensure the provided object is an exception
60
- if not isinstance(e, BaseException):
61
- raise TypeError(f"Expected BaseException, got {type(e).__name__}")
60
+ if not isinstance(exception, BaseException):
61
+ raise TypeError(f"Expected BaseException, got {type(exception).__name__}")
62
62
 
63
63
  # Convert the exception into a structured Throwable object
64
- throwable = await self.destructureException(e)
64
+ throwable = await self.destructureException(exception)
65
65
 
66
66
  # Check if the exception type is in the list of exceptions to ignore
67
67
  return hasattr(self, 'dont_catch') and throwable.classtype in self.dont_catch
orionis/failure/catch.py CHANGED
@@ -1,17 +1,12 @@
1
1
  from typing import Any
2
2
  from orionis.console.kernel import KernelCLI
3
- from orionis.console.output.contracts.console import IConsole
4
3
  from orionis.console.tasks.schedule import Schedule
5
4
  from orionis.failure.contracts.catch import ICatch
6
5
  from orionis.failure.contracts.handler import IBaseExceptionHandler
7
6
  from orionis.foundation.contracts.application import IApplication
8
- from orionis.services.asynchrony.coroutines import Coroutine
9
- from orionis.services.log.contracts.log_service import ILogger
10
- import asyncio
11
7
 
12
8
  class Catch(ICatch):
13
9
 
14
-
15
10
  def __init__(self, app: IApplication) -> None:
16
11
  """
17
12
  Initializes the Catch handler with application services for console output and logging.
@@ -40,11 +35,8 @@ class Catch(ICatch):
40
35
  error reporting and output.
41
36
  """
42
37
 
43
- # Retrieve the console output service from the application container
44
- self.__console: IConsole = app.make('x-orionis.console.output.console')
45
-
46
- # Retrieve the logger service from the application container
47
- self.__logger: ILogger = app.make('x-orionis.services.log.log_service')
38
+ # Store the application instance
39
+ self.__app: IApplication = app
48
40
 
49
41
  # Retrieve the console output service from the application container
50
42
  self.__exception_handler: IBaseExceptionHandler = app.getExceptionHandler()
@@ -76,89 +68,15 @@ class Catch(ICatch):
76
68
  If a valid kernel is provided, the exception details are rendered to the CLI.
77
69
  """
78
70
 
79
- async def handle():
80
- """
81
- Handles exceptions asynchronously using the provided exception handler.
82
-
83
- This method performs the following steps:
84
- 1. Determines if the exception should be ignored by invoking the `shouldIgnoreException` method
85
- of the exception handler. This method supports both coroutine and regular functions.
86
- 2. Reports the exception using the `report` method of the exception handler. This method
87
- also supports both coroutine and regular functions.
88
- 3. If the kernel is of type `KernelCLI` or `Any`, it attempts to render the exception
89
- for the CLI using the `renderCLI` method of the exception handler. This method supports
90
- both coroutine and regular functions.
91
-
92
- Parameters
93
- ----------
94
- None
95
-
96
- Returns
97
- -------
98
- Any or None
99
- The result of the `renderCLI` method if applicable, otherwise `None`.
100
-
101
- Notes
102
- -----
103
- - The `self.__exception_handler` is expected to have the methods `shouldIgnoreException`,
104
- `report`, and `renderCLI`.
105
- - The `self.__logger` is passed to the `report` and `renderCLI` methods for logging purposes.
106
- - The `self.__console` is passed to the `renderCLI` method for CLI rendering.
107
- """
108
-
109
- # Check if the `shouldIgnoreException` method is a coroutine function
110
- if asyncio.iscoroutinefunction(self.__exception_handler.shouldIgnoreException):
111
-
112
- # If it is, await its result to determine if the exception should be ignored
113
- if await self.__exception_handler.shouldIgnoreException(e):
114
- return
115
-
116
- else:
117
-
118
- # If it is not a coroutine, call it directly
119
- if self.__exception_handler.shouldIgnoreException(e):
120
- return
121
-
122
- # Check if the `report` method is a coroutine function
123
- if asyncio.iscoroutinefunction(self.__exception_handler.report):
124
-
125
- # If it is, await its execution to report the exception
126
- await self.__exception_handler.report(
127
- exception=e,
128
- log=self.__logger
129
- )
130
-
131
- else:
132
-
133
- # If it is not a coroutine, call it directly
134
- self.__exception_handler.report(
135
- exception=e,
136
- log=self.__logger
137
- )
138
-
139
- # Check if the kernel is of type `KernelCLI` or `Any`
140
- if isinstance(kernel, KernelCLI) or isinstance(kernel, Schedule):
141
-
142
- # Check if the `renderCLI` method is a coroutine function
143
- if asyncio.iscoroutinefunction(self.__exception_handler.renderCLI):
144
-
145
- # If it is, await its execution to render the exception for the CLI
146
- return await self.__exception_handler.renderCLI(
147
- request=request,
148
- exception=e,
149
- log=self.__logger,
150
- console=self.__console
151
- )
152
-
153
- else:
154
-
155
- # If it is not a coroutine, call it directly
156
- return self.__exception_handler.renderCLI(
157
- request=request,
158
- exception=e,
159
- log=self.__logger,
160
- console=self.__console
161
- )
162
-
163
- # Execute the exception handling logic using the Coroutine wrapper
164
- Coroutine(handle()).run()
71
+ # If there is no exception handler, return early
72
+ if self.__app.call(self.__exception_handler, 'shouldIgnoreException', exception=e):
73
+ return
74
+
75
+ # Report the exception using the exception handler and logger
76
+ self.__app.call(self.__exception_handler, 'report', exception=e)
77
+
78
+ # Check if the kernel is of type `KernelCLI` or `Any`
79
+ if isinstance(kernel, KernelCLI) or isinstance(kernel, Schedule):
80
+
81
+ # Render the exception details to the CLI using the exception handler
82
+ self.__app.call(self.__exception_handler, 'renderCLI', request=request, exception=e)
@@ -1,6 +1,6 @@
1
1
  from dataclasses import dataclass
2
2
 
3
- @dataclass(kw_only=True)
3
+ @dataclass(kw_only=True, frozen=True)
4
4
  class Throwable:
5
5
  """
6
6
  Represents a throwable entity (such as an exception or error) within the framework.
@@ -469,10 +469,10 @@ class Application(Container, IApplication):
469
469
  if self.__exception_handler is None:
470
470
 
471
471
  # Return the default exception handler instance
472
- return BaseExceptionHandler()
472
+ return self.make(BaseExceptionHandler)
473
473
 
474
474
  # Instantiate and return the registered exception handler
475
- return self.__exception_handler()
475
+ return self.make(self.__exception_handler)
476
476
 
477
477
  def setScheduler(
478
478
  self,
@@ -5,7 +5,7 @@
5
5
  NAME = "orionis"
6
6
 
7
7
  # Current version of the framework
8
- VERSION = "0.535.0"
8
+ VERSION = "0.537.0"
9
9
 
10
10
  # Full name of the author or maintainer of the project
11
11
  AUTHOR = "Raul Mauricio Uñate Castro"
@@ -43,6 +43,9 @@ class ResolveArguments(BaseEntity):
43
43
  # Unresolved dependencies as a dictionary of names to Argument instances
44
44
  unresolved: Dict[str, Argument]
45
45
 
46
+ # All dependencies in the order they were defined
47
+ ordered: Dict[str, Argument]
48
+
46
49
  def __post_init__(self):
47
50
  """
48
51
  Validates the types and contents of the resolved and unresolved attributes.
@@ -79,4 +82,11 @@ class ResolveArguments(BaseEntity):
79
82
  if not isinstance(self.unresolved, dict):
80
83
  raise ReflectionTypeError(
81
84
  f"'unresolved' must be a dict, got {type(self.unresolved).__name__}"
85
+ )
86
+
87
+ # Validate that the 'ordered' attribute is a dictionary type
88
+ # This ensures that all dependencies maintain the same structure as resolved ones
89
+ if not isinstance(self.ordered, dict):
90
+ raise ReflectionTypeError(
91
+ f"'ordered' must be a dict, got {type(self.ordered).__name__}"
82
92
  )
@@ -121,6 +121,7 @@ class ReflectDependencies(IReflectDependencies):
121
121
  # Initialize dictionaries to store categorized dependencies
122
122
  resolved_dependencies: Dict[str, Argument] = {}
123
123
  unresolved_dependencies: Dict[str, Argument] = {}
124
+ ordered_dependencies: Dict[str, Argument] = {}
124
125
 
125
126
  # Iterate through all parameters in the signature
126
127
  for param_name, param in signature.parameters.items():
@@ -140,6 +141,7 @@ class ReflectDependencies(IReflectDependencies):
140
141
  type=Any,
141
142
  full_class_path=None,
142
143
  )
144
+ ordered_dependencies[param_name] = unresolved_dependencies[param_name]
143
145
  continue
144
146
 
145
147
  # Case 2: Parameters with default values
@@ -153,6 +155,7 @@ class ReflectDependencies(IReflectDependencies):
153
155
  full_class_path=f"{type(param.default).__module__}.{type(param.default).__name__}",
154
156
  default=param.default
155
157
  )
158
+ ordered_dependencies[param_name] = resolved_dependencies[param_name]
156
159
  continue
157
160
 
158
161
  # Case 3: Parameters with type annotations
@@ -168,6 +171,7 @@ class ReflectDependencies(IReflectDependencies):
168
171
  type=param.annotation,
169
172
  full_class_path=f"{param.annotation.__module__}.{param.annotation.__name__}"
170
173
  )
174
+ ordered_dependencies[param_name] = unresolved_dependencies[param_name]
171
175
  else:
172
176
  # Non-builtin types with annotations are considered resolved
173
177
  # as they can be instantiated by the dependency injection system
@@ -178,12 +182,14 @@ class ReflectDependencies(IReflectDependencies):
178
182
  type=param.annotation,
179
183
  full_class_path=f"{param.annotation.__module__}.{param.annotation.__name__}"
180
184
  )
185
+ ordered_dependencies[param_name] = resolved_dependencies[param_name]
181
186
  continue
182
187
 
183
188
  # Return the categorized dependencies
184
189
  return ResolveArguments(
185
190
  resolved=resolved_dependencies,
186
- unresolved=unresolved_dependencies
191
+ unresolved=unresolved_dependencies,
192
+ ordered=ordered_dependencies
187
193
  )
188
194
 
189
195
  def getConstructorDependencies(self) -> ResolveArguments:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: orionis
3
- Version: 0.535.0
3
+ Version: 0.537.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
@@ -22,7 +22,7 @@ orionis/console/commands/version.py,sha256=SUuNDJ40f2uq69OQUmPQXJKaa9Bm_iVRDPmBd
22
22
  orionis/console/commands/workflow.py,sha256=NYOmjTSvm2o6AE4h9LSTZMFSYPQreNmEJtronyOxaYk,2451
23
23
  orionis/console/contracts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
24
  orionis/console/contracts/command.py,sha256=vmAJD0yMQ5-AD_s9_xCFEAl64sKk65z7U2E196dALQM,5760
25
- orionis/console/contracts/event.py,sha256=zaOkbRNKpU6U_cIEFic84C3WIcD8kCIyX9hc50jN8N0,119797
25
+ orionis/console/contracts/event.py,sha256=GEgpmsyVGKJYi0LwvEklTm7aAHqsqUprtC2p6qq0eZU,120005
26
26
  orionis/console/contracts/kernel.py,sha256=mh4LlhEYHh3FuGZZQ0GBhD6ZLa5YQvaNj2r01IIHI5Y,826
27
27
  orionis/console/contracts/reactor.py,sha256=Xeq7Zrw6WE5MV_XOQfiQEchAFbb6-0TjLpjWOxYW--g,4554
28
28
  orionis/console/contracts/schedule.py,sha256=17cfPYtLo-jqF8FxYOhh4epJZnxw5mMPuLGaWoUwxL4,12171
@@ -65,7 +65,7 @@ orionis/console/entities/scheduler_shutdown.py,sha256=vRFXUt3hxKueaT6M4335HFL3NO
65
65
  orionis/console/entities/scheduler_started.py,sha256=U0pduaFhWrEUJQws81jTjDmTp4Kxfy6ontq8rQN4Y6I,910
66
66
  orionis/console/enums/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
67
67
  orionis/console/enums/command.py,sha256=lCfVp2vnDojJN2gjdVxE_XU3mRjZZgOIxPfBVQYo9w4,1278
68
- orionis/console/enums/event.py,sha256=G_6QEagrjtQ81dgkVhTTc2M2YuhnnKRkRSjd2i8m3OQ,2808
68
+ orionis/console/enums/event.py,sha256=xyJfJQmXnZ_c-zPZx3_pzKm54R-kKyJkQzIercv556w,3004
69
69
  orionis/console/enums/listener.py,sha256=bDHDOkKQFEfEmZyiisZj37ozr8Hs7_lKvm_3uYjvEpw,2988
70
70
  orionis/console/exceptions/__init__.py,sha256=0qlHNuHMVZO87M-rP8lThUUyljRM1jSFNikaxSCjSbw,366
71
71
  orionis/console/exceptions/cli_exception.py,sha256=HsZ_vSeNiJWQ0gznVFNcIdhM0Bj_vkSRVBJs0wMjEKY,1141
@@ -82,16 +82,16 @@ orionis/console/output/enums/__init__.py,sha256=LAaAxg-DpArCjf_jqZ0_9s3p8899gntD
82
82
  orionis/console/output/enums/styles.py,sha256=6a4oQCOBOKMh2ARdeq5GlIskJ3wjiylYmh66tUKKmpQ,4053
83
83
  orionis/console/request/cli_request.py,sha256=7-sgYmNUCipuHLVAwWLJiHv0cJCDmsM1Lu9s2D8RIII,1498
84
84
  orionis/console/tasks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
85
- orionis/console/tasks/event.py,sha256=l4J-HEPaj1mxB_PYQMgG9dRHUe01wUag8fKLLnR2N2M,164395
85
+ orionis/console/tasks/event.py,sha256=yE64gWAWaaz-mlWk5LIKDh4HYO14iwRfdYfwFh7Wktk,166479
86
86
  orionis/console/tasks/listener.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
87
- orionis/console/tasks/schedule.py,sha256=lou90LaOWw4_tw2ONrplVOEUI9A5esKspFL900EIUBI,82842
87
+ orionis/console/tasks/schedule.py,sha256=7aUDyRKvvLMnWHZccqvSYr9oZYFcl4hzsh5amd3X_E4,82392
88
88
  orionis/container/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
89
- orionis/container/container.py,sha256=aF_b6lTUpG4YCo9yFJEzsntTdIzgMMXFW5LyWqAJVBQ,87987
89
+ orionis/container/container.py,sha256=262gahG9L_72QovlHP7cF5QhFX4HTolwcYrVo86HcVg,93196
90
90
  orionis/container/context/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
91
91
  orionis/container/context/manager.py,sha256=I08K_jKXSKmrq18Pv33qYyMKIlAovVOwIgmfiVm-J7c,2971
92
92
  orionis/container/context/scope.py,sha256=p_oCzR7dDz-5ZAd16ab4vfLc3gBf34CaN0f4iR9D0bQ,1155
93
93
  orionis/container/contracts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
94
- orionis/container/contracts/container.py,sha256=l-wVi6KMLL4I55JSHnNqykiaZSZO9ea6BDSzCKsysak,14217
94
+ orionis/container/contracts/container.py,sha256=r-HpuMUbkI19lNTisgxB2lbvvt0YFA4xsEDVLCPDrys,15507
95
95
  orionis/container/contracts/service_provider.py,sha256=TJtwFoPYvw3hvBSfIEO3-httq8iszaRQh1IjCKfDyVM,1016
96
96
  orionis/container/entities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
97
97
  orionis/container/entities/binding.py,sha256=sY0ioHbRcjp9TSQjfrFHxkO3vRn_VOrbHK62_QEGe1U,3717
@@ -117,16 +117,16 @@ orionis/container/validators/is_subclass.py,sha256=4sBaGLoRs8nUhuWjlP0VJqyTwVHYq
117
117
  orionis/container/validators/is_valid_alias.py,sha256=4uAYcq8xov7jZbXnpKpjNkxcZtlTNnL5RRctVPMwJes,1424
118
118
  orionis/container/validators/lifetime.py,sha256=IQ43fDNrxYHMlZH2zlYDJnlkLO_eS4U7Fs3UJgQBidI,1844
119
119
  orionis/failure/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
120
- orionis/failure/catch.py,sha256=kmw2mbkGrFNw9s8jrr2NdJeGlbJVvXQQSlnjltwC4oM,6819
120
+ orionis/failure/catch.py,sha256=XpPISy-83grGYP1PSScrF7jyogpvOjDjbz-11iop2FQ,3286
121
121
  orionis/failure/base/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
122
- orionis/failure/base/handler.py,sha256=a7fQJMWVjALwFiITRsMYg3s7WSO6zcbmq171iQRWy48,4867
122
+ orionis/failure/base/handler.py,sha256=6QU2XHpJgH2hhGBANvioJBz16Aoi9Xy6QEF1PJBGLDQ,4939
123
123
  orionis/failure/contracts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
124
124
  orionis/failure/contracts/catch.py,sha256=e2wM1p6VxbvAjgWm-MwoM9p2ystSsyBu8Qnt6Ehr6Vc,1179
125
125
  orionis/failure/contracts/handler.py,sha256=drNE8iu8RUHi3TgKn-lUEIfVsZeGsMTUecTZOfay19E,2240
126
126
  orionis/failure/entities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
127
- orionis/failure/entities/throwable.py,sha256=ogys062uhim5QMYU62ezlnumRAnYQlUf_vZvQY47S3U,1227
127
+ orionis/failure/entities/throwable.py,sha256=1zD-awcuAyEtlR-L7V7ZIfPSF4GpXkf-neL5sXul7dc,1240
128
128
  orionis/foundation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
129
- orionis/foundation/application.py,sha256=RwMRjRp9kkKRbDsz_lAN4NYadCuaDbmDnccWKl8aKng,86488
129
+ orionis/foundation/application.py,sha256=_I8EAPcsRmGdmG9ajdSHovwny_tsELXWwvej6WZQj0A,86506
130
130
  orionis/foundation/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
131
131
  orionis/foundation/config/startup.py,sha256=vbzduprRCNyYeR2nnMaqc1uKXw6PTzAY2jVfXNQKN8I,9691
132
132
  orionis/foundation/config/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -242,7 +242,7 @@ orionis/foundation/providers/scheduler_provider.py,sha256=1do4B09bU_6xbFHHVYYTGM
242
242
  orionis/foundation/providers/testing_provider.py,sha256=SrJRpdvcblx9WvX7x9Y3zc7OQfiTf7la0HAJrm2ESlE,3725
243
243
  orionis/foundation/providers/workers_provider.py,sha256=oa_2NIDH6UxZrtuGkkoo_zEoNIMGgJ46vg5CCgAm7wI,3926
244
244
  orionis/metadata/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
245
- orionis/metadata/framework.py,sha256=uWTlWqt2g9tvRAk56GneEvJAcpVopbXX5L67RJzuXX8,4109
245
+ orionis/metadata/framework.py,sha256=_yk_F1NMdYLo-ucW1aLKfOQd_AXew-3YEx-XHDhR8sY,4109
246
246
  orionis/metadata/package.py,sha256=k7Yriyp5aUcR-iR8SK2ec_lf0_Cyc-C7JczgXa-I67w,16039
247
247
  orionis/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
248
248
  orionis/services/asynchrony/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -298,12 +298,12 @@ orionis/services/introspection/concretes/contracts/reflection.py,sha256=LwEAgdN_
298
298
  orionis/services/introspection/dataclass/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
299
299
  orionis/services/introspection/dataclass/attributes.py,sha256=VGBUxH40PxUE8J4TBBYdSqwTO0xwYYmIS6BITgff-cE,1695
300
300
  orionis/services/introspection/dependencies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
301
- orionis/services/introspection/dependencies/reflection.py,sha256=VEe3cY6LTpOW3dm0IFECTHh7F_9_X-siTAeQc2XRQeM,13451
301
+ orionis/services/introspection/dependencies/reflection.py,sha256=3aQl541JkohRcoZFxQGiGPz3CnBMK8gmcRwO9XiLSY0,13906
302
302
  orionis/services/introspection/dependencies/contracts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
303
303
  orionis/services/introspection/dependencies/contracts/reflection.py,sha256=DhkAmsl8fxNr3L1kCSpVmTZx9YBejq2kYcfUrCWG8o0,5364
304
304
  orionis/services/introspection/dependencies/entities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
305
305
  orionis/services/introspection/dependencies/entities/argument.py,sha256=o30iMSghV8CFBWmnM__8_IOnOGrCLZfRUIQGD2GVl_g,4188
306
- orionis/services/introspection/dependencies/entities/resolve_argument.py,sha256=GxlW-JwoJwYO1TWH4aqJ982Y4mTiU69GEU3GlevDWtM,3373
306
+ orionis/services/introspection/dependencies/entities/resolve_argument.py,sha256=einKDEIp9NjBUfc9_ulmkwzAv7sRuxS3lM_FE3zFN-s,3810
307
307
  orionis/services/introspection/exceptions/__init__.py,sha256=oe7uzaGwjeMgz62ZTeNn6ersCApOVv3PXFlUw0vYLQM,234
308
308
  orionis/services/introspection/exceptions/attribute.py,sha256=_JerTjfhikYwC2FKJV8XbVZqiZdLcBt7WGtw8r6vkqo,473
309
309
  orionis/services/introspection/exceptions/type.py,sha256=73DB8JoTbxd7LNMnZKXf4jJ8OzLyC0HPXgDo5pffkYY,466
@@ -420,7 +420,7 @@ orionis/test/validators/web_report.py,sha256=n9BfzOZz6aEiNTypXcwuWbFRG0OdHNSmCNu
420
420
  orionis/test/validators/workers.py,sha256=rWcdRexINNEmGaO7mnc1MKUxkHKxrTsVuHgbnIfJYgc,1206
421
421
  orionis/test/view/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
422
422
  orionis/test/view/render.py,sha256=f-zNhtKSg9R5Njqujbg2l2amAs2-mRVESneLIkWOZjU,4082
423
- orionis-0.535.0.dist-info/licenses/LICENCE,sha256=JhC-z_9mbpUrCfPjcl3DhDA8trNDMzb57cvRSam1avc,1463
423
+ orionis-0.537.0.dist-info/licenses/LICENCE,sha256=JhC-z_9mbpUrCfPjcl3DhDA8trNDMzb57cvRSam1avc,1463
424
424
  tests/container/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
425
425
  tests/container/context/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
426
426
  tests/container/context/test_manager.py,sha256=wOwXpl9rHNfTTexa9GBKYMwK0_-KSQPbI-AEyGNkmAE,1356
@@ -566,8 +566,8 @@ tests/testing/validators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZ
566
566
  tests/testing/validators/test_testing_validators.py,sha256=WPo5GxTP6xE-Dw3X1vZoqOMpb6HhokjNSbgDsDRDvy4,16588
567
567
  tests/testing/view/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
568
568
  tests/testing/view/test_render.py,sha256=tnnMBwS0iKUIbogLvu-7Rii50G6Koddp3XT4wgdFEYM,1050
569
- orionis-0.535.0.dist-info/METADATA,sha256=fgBBQFe8aw1uvm0D0Wr1yobSyOoPlEzaiEaylQpTSd0,4801
570
- orionis-0.535.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
571
- orionis-0.535.0.dist-info/top_level.txt,sha256=2bdoHgyGZhOtLAXS6Om8OCTmL24dUMC_L1quMe_ETbk,14
572
- orionis-0.535.0.dist-info/zip-safe,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
573
- orionis-0.535.0.dist-info/RECORD,,
569
+ orionis-0.537.0.dist-info/METADATA,sha256=XK4A6ii-FE63V5-792FaTkZt6wsrkq-SREBrzuvazwU,4801
570
+ orionis-0.537.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
571
+ orionis-0.537.0.dist-info/top_level.txt,sha256=2bdoHgyGZhOtLAXS6Om8OCTmL24dUMC_L1quMe_ETbk,14
572
+ orionis-0.537.0.dist-info/zip-safe,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
573
+ orionis-0.537.0.dist-info/RECORD,,