orionis 0.532.0__py3-none-any.whl → 0.534.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.
@@ -0,0 +1,57 @@
1
+ from orionis.console.base.command import BaseCommand
2
+ from orionis.console.contracts.reactor import IReactor
3
+ from orionis.console.exceptions import CLIOrionisRuntimeError
4
+ from rich.console import Console
5
+ from rich.panel import Panel
6
+
7
+ class MakeListenerCommand(BaseCommand):
8
+ """
9
+ Este comando se encarga de crear los listener tando para CLI como para otro tipo de Eventos.
10
+ """
11
+
12
+ # Indicates whether timestamps will be shown in the command output
13
+ timestamps: bool = False
14
+
15
+ # Command signature and description
16
+ signature: str = "make:listener"
17
+
18
+ # Command description
19
+ description: str = "Displays usage information, examples, and a list of available commands in the Orionis CLI."
20
+
21
+ def handle(self) -> dict:
22
+ """
23
+ Displays usage information and a list of available commands for the Orionis CLI.
24
+
25
+ Parameters
26
+ ----------
27
+ reactor : IReactor
28
+ The reactor instance providing command metadata via the `info()` method.
29
+
30
+ Returns
31
+ -------
32
+ dict
33
+ A dictionary containing the list of available commands, each with its signature and description.
34
+
35
+ Raises
36
+ ------
37
+ CLIOrionisRuntimeError
38
+ If an unexpected error occurs during help information generation or display.
39
+ """
40
+ try:
41
+
42
+ # Solicitar el nombre del listener al usuario
43
+ ans = self.choice(
44
+ question="Tipo de Listener",
45
+ choices=[
46
+ "Listener para eventos CLI",
47
+ "Listener para eventos de Schedule",
48
+ ],
49
+ default_index=0
50
+ )
51
+
52
+ print(ans)
53
+
54
+ except Exception as e:
55
+
56
+ # Raise a custom runtime error if any exception occurs
57
+ raise CLIOrionisRuntimeError(f"An unexpected error occurred: {e}") from e
@@ -114,6 +114,7 @@ class Reactor(IReactor):
114
114
  from orionis.console.commands.cache import CacheClearCommand
115
115
  from orionis.console.commands.scheduler_work import ScheduleWorkCommand
116
116
  from orionis.console.commands.scheduler_list import ScheduleListCommand
117
+ from orionis.console.commands.make_listener import MakeListenerCommand
117
118
 
118
119
  # List of core command classes to load (extend this list as more core commands are added)
119
120
  core_commands = [
@@ -124,7 +125,8 @@ class Reactor(IReactor):
124
125
  WorkFlowGithubCommand,
125
126
  CacheClearCommand,
126
127
  ScheduleWorkCommand,
127
- ScheduleListCommand
128
+ ScheduleListCommand,
129
+ MakeListenerCommand
128
130
  ]
129
131
 
130
132
  # Iterate through the core command classes and register them
@@ -37,6 +37,7 @@ from orionis.console.exceptions import CLIOrionisRuntimeError
37
37
  from orionis.console.exceptions.cli_orionis_value_error import CLIOrionisValueError
38
38
  from orionis.failure.contracts.catch import ICatch
39
39
  from orionis.foundation.contracts.application import IApplication
40
+ from orionis.services.asynchrony.coroutines import Coroutine
40
41
  from orionis.services.log.contracts.log_service import ILogger
41
42
 
42
43
  class Schedule(ISchedule):
@@ -478,44 +479,13 @@ class Schedule(ISchedule):
478
479
  # If is Callable
479
480
  if callable(listener):
480
481
 
482
+ # Invoke the listener, handling both coroutine and regular functions
481
483
  try:
482
484
 
483
- # If the listener is a coroutine, handle it properly
484
- if asyncio.iscoroutinefunction(listener):
485
+ # Use Coroutine to handle both sync and async listeners
486
+ Coroutine(listener).invoke(event_data, self)
485
487
 
486
- try:
487
-
488
- # Try to get the current running event loop
489
- loop = asyncio.get_running_loop()
490
-
491
- # Schedule the coroutine as a task in the current loop
492
- loop.create_task(listener(event_data, self))
493
-
494
- except RuntimeError:
495
-
496
- # No running event loop, create a new one
497
- try:
498
-
499
- # Create and run the coroutine in a new event loop
500
- asyncio.run(listener(event_data, self))
501
-
502
- except Exception as e:
503
-
504
- # Handle exceptions during coroutine execution
505
- error_msg = f"Failed to run async listener for '{scheduler_event}': {str(e)}"
506
- self.__logger.error(error_msg)
507
- raise CLIOrionisRuntimeError(error_msg) from e
508
-
509
- else:
510
-
511
- # Call the regular listener function directly
512
- listener(event_data, self)
513
-
514
- except Exception as e:
515
-
516
- # Re-raise CLIOrionisRuntimeError exceptions
517
- if isinstance(e, CLIOrionisRuntimeError):
518
- raise e
488
+ except BaseException as e:
519
489
 
520
490
  # Construct and log error message
521
491
  error_msg = f"An error occurred while invoking the listener for event '{scheduler_event}': {str(e)}"
@@ -588,45 +558,14 @@ class Schedule(ISchedule):
588
558
  # Retrieve the method from the listener
589
559
  listener_method = getattr(listener, scheduler_event)
590
560
 
561
+ # Invoke the listener method, handling both coroutine and regular functions
591
562
  try:
592
563
 
593
- # If the listener_method is a coroutine, handle it properly
594
- if asyncio.iscoroutinefunction(listener_method):
595
-
596
- try:
597
-
598
- # Try to get the current running event loop
599
- loop = asyncio.get_running_loop()
600
-
601
- # Schedule the coroutine as a task in the current loop
602
- loop.create_task(listener_method(event_data, self))
603
-
604
- except RuntimeError:
605
-
606
- # No running event loop, create a new one
607
- try:
608
-
609
- # Create and run the coroutine in a new event loop
610
- asyncio.run(listener_method(event_data, self))
611
-
612
- except Exception as e:
613
-
614
- # Handle exceptions during coroutine execution
615
- error_msg = f"Failed to run async listener_method for '{scheduler_event}': {str(e)}"
616
- self.__logger.error(error_msg)
617
- raise CLIOrionisRuntimeError(error_msg) from e
618
-
619
- else:
620
-
621
- # Call the regular listener_method function directly
622
- listener_method(event_data, self)
564
+ # Use Coroutine to handle both sync and async listener methods
565
+ Coroutine(listener_method).invoke(event_data, self)
623
566
 
624
567
  except Exception as e:
625
568
 
626
- # Re-raise CLIOrionisRuntimeError exceptions
627
- if isinstance(e, CLIOrionisRuntimeError):
628
- raise e
629
-
630
569
  # Construct and log error message
631
570
  error_msg = f"An error occurred while invoking the listener_method for event '{scheduler_event}': {str(e)}"
632
571
  self.__logger.error(error_msg)
@@ -1081,52 +1020,99 @@ class Schedule(ISchedule):
1081
1020
  func: Callable[..., Awaitable[Any]]
1082
1021
  ) -> Callable[..., Any]:
1083
1022
  """
1084
- Wrap an asynchronous function to be called in a synchronous context.
1023
+ Wrap an asynchronous function to be executed in a synchronous context.
1085
1024
 
1086
- This method takes an asynchronous function (a coroutine) and returns a synchronous
1087
- wrapper function that can be called in a non-async context. The wrapper function
1088
- ensures that the asynchronous function is executed within the appropriate event loop.
1025
+ This method creates a synchronous wrapper around an asynchronous function (coroutine)
1026
+ that enables its execution within non-async contexts. The wrapper leverages the
1027
+ Coroutine utility class to handle the complexities of asyncio event loop management
1028
+ and provides proper error handling with detailed logging and custom exception
1029
+ propagation.
1030
+
1031
+ The wrapper is particularly useful when integrating asynchronous functions with
1032
+ synchronous APIs or frameworks that do not natively support async operations,
1033
+ such as the APScheduler job execution environment.
1089
1034
 
1090
1035
  Parameters
1091
1036
  ----------
1092
1037
  func : Callable[..., Awaitable[Any]]
1093
- The asynchronous function (coroutine) to be wrapped. This function should be
1094
- defined using the `async def` syntax and return an awaitable object.
1038
+ The asynchronous function (coroutine) to be wrapped. This function must be
1039
+ defined using the `async def` syntax and return an awaitable object. The
1040
+ function can accept any number of positional and keyword arguments.
1095
1041
 
1096
1042
  Returns
1097
1043
  -------
1098
1044
  Callable[..., Any]
1099
- A synchronous wrapper function that can be called in a non-async context.
1100
- When invoked, this wrapper will execute the original asynchronous function
1101
- within the appropriate event loop.
1045
+ A synchronous wrapper function that executes the original asynchronous
1046
+ function and returns its result. The wrapper function accepts the same
1047
+ arguments as the original async function and forwards them appropriately.
1048
+ The return type depends on what the wrapped asynchronous function returns.
1102
1049
 
1103
1050
  Raises
1104
1051
  ------
1105
1052
  CLIOrionisRuntimeError
1106
- If the provided `func` is not an asynchronous function or execution fails.
1053
+ If the asynchronous function execution fails or if the provided `func`
1054
+ parameter is not a valid asynchronous function. The original exception
1055
+ is wrapped to provide additional context for debugging.
1056
+
1057
+ Notes
1058
+ -----
1059
+ This method relies on the Coroutine utility class from the orionis.services.asynchrony
1060
+ module to handle the execution of the wrapped asynchronous function. The wrapper
1061
+ uses the instance logger for comprehensive error reporting and debugging information.
1107
1062
  """
1108
1063
 
1109
1064
  def sync_wrapper(*args, **kwargs) -> Any:
1065
+ """
1066
+ Synchronous wrapper function that executes asynchronous functions in a thread-safe manner.
1067
+
1068
+ This wrapper provides a uniform interface for executing asynchronous functions within
1069
+ synchronous contexts by leveraging the Coroutine utility class. It handles the complexity
1070
+ of managing async/await patterns and provides proper error handling with detailed logging
1071
+ and custom exception propagation.
1072
+
1073
+ The wrapper is particularly useful when integrating asynchronous functions with
1074
+ synchronous APIs or frameworks that do not natively support async operations.
1075
+
1076
+ Parameters
1077
+ ----------
1078
+ *args : tuple
1079
+ Variable length argument list to pass to the wrapped asynchronous function.
1080
+ These arguments are forwarded directly to the original function.
1081
+ **kwargs : dict
1082
+ Arbitrary keyword arguments to pass to the wrapped asynchronous function.
1083
+ These keyword arguments are forwarded directly to the original function.
1084
+
1085
+ Returns
1086
+ -------
1087
+ Any
1088
+ The return value from the executed asynchronous function. The type depends
1089
+ on what the wrapped function returns and can be any Python object.
1090
+
1091
+ Raises
1092
+ ------
1093
+ CLIOrionisRuntimeError
1094
+ When the asynchronous function execution fails, wrapping the original
1095
+ exception with additional context and error details for better debugging.
1096
+
1097
+ Notes
1098
+ -----
1099
+ This function relies on the Coroutine utility class to handle the execution
1100
+ of the wrapped asynchronous function and uses the instance logger for comprehensive
1101
+ error reporting and debugging information.
1102
+ """
1110
1103
 
1111
1104
  try:
1112
-
1113
- # Try to get the current event loop
1114
- try:
1115
-
1116
- # If we're already in an event loop, create a task
1117
- loop = asyncio.get_running_loop()
1118
- task = loop.create_task(func(*args, **kwargs))
1119
- return task
1120
-
1121
- except RuntimeError:
1122
-
1123
- # No running loop, so we can run the coroutine directly
1124
- return asyncio.run(func(*args, **kwargs))
1105
+ # Execute the asynchronous function using the Coroutine utility class
1106
+ # The Coroutine class handles the event loop management and async execution
1107
+ return Coroutine(func).invoke(*args, **kwargs)
1125
1108
 
1126
1109
  except Exception as e:
1127
1110
 
1128
- # Log the error and re-raise
1111
+ # Log the error with detailed information for debugging purposes
1129
1112
  self.__logger.error(f"Error executing async function: {str(e)}")
1113
+
1114
+ # Re-raise the exception wrapped in a custom exception type
1115
+ # This provides better error context and maintains the original stack trace
1130
1116
  raise CLIOrionisRuntimeError(f"Failed to execute async function: {str(e)}") from e
1131
1117
 
1132
1118
  # Return the synchronous wrapper function
@@ -8,12 +8,12 @@ from orionis.services.log.contracts.log_service import ILogger
8
8
  class BaseExceptionHandler(IBaseExceptionHandler):
9
9
 
10
10
  # Exceptions that should not be caught by the handler
11
- dont_cathc: List[type[BaseException]] = [
11
+ dont_catch: List[type[BaseException]] = [
12
12
  # Add specific exceptions that should not be caught
13
13
  # Example: OrionisContainerException
14
14
  ]
15
15
 
16
- def destructureException(self, e: BaseException) -> Throwable:
16
+ async def destructureException(self, e: BaseException) -> Throwable:
17
17
  """
18
18
  Converts an exception into a structured `Throwable` object containing detailed information.
19
19
 
@@ -41,7 +41,7 @@ class BaseExceptionHandler(IBaseExceptionHandler):
41
41
  traceback=getattr(e, '__traceback__', None) # The traceback object, if available
42
42
  )
43
43
 
44
- def shouldIgnoreException(self, e: BaseException) -> bool:
44
+ async def shouldIgnoreException(self, e: BaseException) -> bool:
45
45
  """
46
46
  Determines if the exception should be ignored (not handled) by the handler.
47
47
 
@@ -61,12 +61,12 @@ class BaseExceptionHandler(IBaseExceptionHandler):
61
61
  raise TypeError(f"Expected BaseException, got {type(e).__name__}")
62
62
 
63
63
  # Convert the exception into a structured Throwable object
64
- throwable = self.destructureException(e)
64
+ throwable = await self.destructureException(e)
65
65
 
66
66
  # Check if the exception type is in the list of exceptions to ignore
67
- return hasattr(self, 'dont_cathc') and throwable.classtype in self.dont_cathc
67
+ return hasattr(self, 'dont_catch') and throwable.classtype in self.dont_catch
68
68
 
69
- def report(self, exception: BaseException, log: ILogger) -> Any:
69
+ async def report(self, exception: BaseException, log: ILogger) -> Any:
70
70
  """
71
71
  Report or log an exception.
72
72
 
@@ -84,7 +84,7 @@ class BaseExceptionHandler(IBaseExceptionHandler):
84
84
  raise TypeError(f"Expected BaseException, got {type(exception).__name__}")
85
85
 
86
86
  # Convert the exception into a structured Throwable object
87
- throwable = self.destructureException(exception)
87
+ throwable = await self.destructureException(exception)
88
88
 
89
89
  # Log the exception details
90
90
  log.error(f"[{throwable.classtype.__name__}] {throwable.message}")
@@ -92,7 +92,7 @@ class BaseExceptionHandler(IBaseExceptionHandler):
92
92
  # Return the structured exception
93
93
  return throwable
94
94
 
95
- def renderCLI(self, request: CLIRequest, exception: BaseException, log: ILogger, console: IConsole) -> Any:
95
+ async def renderCLI(self, request: CLIRequest, exception: BaseException, log: ILogger, console: IConsole) -> Any:
96
96
  """
97
97
  Render the exception message for CLI output.
98
98
 
@@ -109,11 +109,15 @@ class BaseExceptionHandler(IBaseExceptionHandler):
109
109
  if not isinstance(exception, BaseException):
110
110
  raise TypeError(f"Expected BaseException, got {type(exception).__name__}")
111
111
 
112
+ # Ensure the request is a CLIRequest
113
+ if not isinstance(request, CLIRequest):
114
+ raise TypeError(f"Expected CLIRequest, got {type(request).__name__}")
115
+
112
116
  # Convert the exception into a structured Throwable object
113
- throwable = self.destructureException(exception)
117
+ throwable = await self.destructureException(exception)
114
118
 
115
- args = request.args if isinstance(request, CLIRequest) and request.args else []
116
- string_args = ' '.join(args)
119
+ # Convert the request arguments to a string for logging
120
+ string_args = ' '.join(request.args)
117
121
 
118
122
  # Log the CLI error message with arguments
119
123
  log.error(f"CLI Error: {throwable.message} (Args: {string_args})")
orionis/failure/catch.py CHANGED
@@ -5,7 +5,9 @@ from orionis.console.tasks.schedule import Schedule
5
5
  from orionis.failure.contracts.catch import ICatch
6
6
  from orionis.failure.contracts.handler import IBaseExceptionHandler
7
7
  from orionis.foundation.contracts.application import IApplication
8
+ from orionis.services.asynchrony.coroutines import Coroutine
8
9
  from orionis.services.log.contracts.log_service import ILogger
10
+ import asyncio
9
11
 
10
12
  class Catch(ICatch):
11
13
 
@@ -74,21 +76,89 @@ class Catch(ICatch):
74
76
  If a valid kernel is provided, the exception details are rendered to the CLI.
75
77
  """
76
78
 
77
- # Check if the exception should be ignored by the handler
78
- if self.__exception_handler.shouldIgnoreException(e):
79
- return
80
-
81
- # Report the exception using the application's exception handler and logger
82
- self.__exception_handler.report(
83
- exception=e,
84
- log=self.__logger
85
- )
86
-
87
- # If a kernel is provided, render the exception details to the CLI
88
- if isinstance(kernel, KernelCLI) or isinstance(kernel, Schedule):
89
- return self.__exception_handler.renderCLI(
90
- request=request,
91
- exception=e,
92
- log=self.__logger,
93
- console=self.__console
94
- )
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()
@@ -7,7 +7,7 @@ from orionis.services.log.contracts.log_service import ILogger
7
7
  class IBaseExceptionHandler:
8
8
 
9
9
  @abstractmethod
10
- def destructureException(self, e: BaseException):
10
+ async def destructureException(self, e: BaseException):
11
11
  """
12
12
  Converts an exception into a structured `Throwable` object containing detailed information.
13
13
 
@@ -29,7 +29,7 @@ class IBaseExceptionHandler:
29
29
  pass
30
30
 
31
31
  @abstractmethod
32
- def shouldIgnoreException(self, e: BaseException) -> bool:
32
+ async def shouldIgnoreException(self, e: BaseException) -> bool:
33
33
  """
34
34
  Determines if the exception should be ignored (not handled) by the handler.
35
35
 
@@ -46,7 +46,7 @@ class IBaseExceptionHandler:
46
46
  pass
47
47
 
48
48
  @abstractmethod
49
- def report (self, exception: BaseException, log: ILogger) -> Any:
49
+ async def report (self, exception: BaseException, log: ILogger) -> Any:
50
50
  """
51
51
  Report or log an exception.
52
52
 
@@ -62,7 +62,7 @@ class IBaseExceptionHandler:
62
62
  pass
63
63
 
64
64
  @abstractmethod
65
- def renderCLI(self, request: CLIRequest, exception: BaseException, log: ILogger, console: IConsole) -> Any:
65
+ async def renderCLI(self, request: CLIRequest, exception: BaseException, log: ILogger, console: IConsole) -> Any:
66
66
  """
67
67
  Render the exception message for CLI output.
68
68
 
@@ -5,7 +5,7 @@
5
5
  NAME = "orionis"
6
6
 
7
7
  # Current version of the framework
8
- VERSION = "0.532.0"
8
+ VERSION = "0.534.0"
9
9
 
10
10
  # Full name of the author or maintainer of the project
11
11
  AUTHOR = "Raul Mauricio Uñate Castro"
@@ -1,5 +1,5 @@
1
1
  import asyncio
2
- from typing import Any, Coroutine as TypingCoroutine, TypeVar, Union
2
+ from typing import Any, Callable, Coroutine as TypingCoroutine, TypeVar, Union
3
3
  from orionis.services.asynchrony.contracts.coroutines import ICoroutine
4
4
  from orionis.services.asynchrony.exceptions import OrionisCoroutineException
5
5
  from orionis.services.introspection.objects.types import Type
@@ -8,29 +8,128 @@ T = TypeVar("T")
8
8
 
9
9
  class Coroutine(ICoroutine):
10
10
 
11
- def __init__(self, func: TypingCoroutine[Any, Any, T]) -> None:
11
+ def __init__(self, func: Union[TypingCoroutine[Any, Any, T], Callable[..., TypingCoroutine[Any, Any, T]]]) -> None:
12
12
  """
13
- Wraps a coroutine object and validates its type.
13
+ Initialize a Coroutine wrapper for managing and executing coroutine objects.
14
+
15
+ This constructor accepts either a coroutine object directly or a callable that
16
+ returns a coroutine when invoked. The wrapped coroutine can later be executed
17
+ using the run() method with automatic context detection.
18
+
19
+ Parameters
20
+ ----------
21
+ func : Union[TypingCoroutine[Any, Any, T], Callable[..., TypingCoroutine[Any, Any, T]]]
22
+ The coroutine object to be wrapped and managed, or a callable that returns
23
+ a coroutine. This will be stored internally for later execution.
24
+
25
+ Returns
26
+ -------
27
+ None
28
+ This is a constructor method and does not return any value.
29
+
30
+ Notes
31
+ -----
32
+ - The coroutine type validation is performed during execution in the run() method,
33
+ not during initialization.
34
+ - Both coroutine objects and coroutine functions are accepted as valid input.
35
+ """
36
+
37
+ # Store the coroutine object or callable for later execution
38
+ self.__func = func
39
+
40
+ def invoke(self, *args, **kwargs) -> Union[T, asyncio.Task, None]:
41
+ """
42
+ Invoke the callable coroutine function with the provided arguments.
43
+
44
+ This method executes a callable coroutine function or regular function with the given
45
+ arguments and keyword arguments. It automatically detects the execution context and
46
+ handles both synchronous and asynchronous execution appropriately.
14
47
 
15
48
  Parameters
16
49
  ----------
17
- func : Coroutine
18
- The coroutine object to be wrapped and managed.
50
+ *args : tuple
51
+ Positional arguments to pass to the callable function.
52
+ **kwargs : dict
53
+ Keyword arguments to pass to the callable function.
54
+
55
+ Returns
56
+ -------
57
+ Union[T, asyncio.Task, None]
58
+ - T: The result of the coroutine if executed synchronously
59
+ - asyncio.Task: A task object if scheduled for asynchronous execution
60
+ - None: If the callable is not a coroutine function
19
61
 
20
62
  Raises
21
63
  ------
22
64
  OrionisCoroutineException
23
- If the provided object is not a coroutine.
24
- """
65
+ If an error occurs during coroutine execution.
66
+ RuntimeError
67
+ If an error occurs during callable execution that is not coroutine-related.
25
68
 
26
- # Validate that the provided object is a coroutine
27
- if not Type(func).isCoroutine():
69
+ Notes
70
+ -----
71
+ - Only callable objects can be invoked with this method
72
+ - For coroutine functions, execution context is automatically detected
73
+ - Non-coroutine callables are executed directly and return None
74
+ - Exceptions are wrapped with appropriate context information
75
+
76
+ Examples
77
+ --------
78
+ >>> async def my_coro(x, y):
79
+ ... return x + y
80
+ >>> coro = Coroutine(my_coro)
81
+ >>> result = coro.invoke(1, 2) # Returns Task or result depending on context
82
+ """
83
+ if not callable(self.__func):
28
84
  raise OrionisCoroutineException(
29
- f"Expected a coroutine object, but got {type(func).__name__}."
85
+ f"Cannot invoke non-callable object of type {type(self.__func).__name__}"
30
86
  )
31
87
 
32
- # Store the coroutine object for later execution
33
- self.__func = func
88
+ try:
89
+
90
+ # Check if the callable is a coroutine function
91
+ if asyncio.iscoroutinefunction(self.__func):
92
+
93
+ # Create the coroutine object
94
+ coroutine_obj = self.__func(*args, **kwargs)
95
+
96
+ try:
97
+
98
+ # Check if we're inside a running event loop
99
+ loop = asyncio.get_running_loop()
100
+ return loop.create_task(coroutine_obj)
101
+
102
+ except RuntimeError:
103
+
104
+ # No running event loop, execute synchronously
105
+ try:
106
+
107
+ # Use asyncio.run to execute the coroutine and return its result
108
+ return asyncio.run(coroutine_obj)
109
+
110
+ except Exception as e:
111
+
112
+ # Wrap and raise any exceptions that occur during execution
113
+ raise OrionisCoroutineException(
114
+ f"Failed to execute coroutine synchronously: {str(e)}"
115
+ ) from e
116
+
117
+ else:
118
+
119
+ # Execute regular callable directly
120
+ return self.__func(*args, **kwargs)
121
+
122
+ except OrionisCoroutineException:
123
+
124
+ # Re-raise our custom exceptions as-is
125
+ raise
126
+
127
+ except Exception as e:
128
+
129
+ # Wrap and raise any other exceptions that occur during invocation
130
+ raise RuntimeError(
131
+ f"Unexpected error during callable invocation: {str(e)}"
132
+ ) from e
34
133
 
35
134
  def run(self) -> Union[T, asyncio.Future]:
36
135
  """
@@ -53,6 +152,12 @@ class Coroutine(ICoroutine):
53
152
  - The method automatically detects the execution context and chooses the appropriate execution strategy.
54
153
  """
55
154
 
155
+ # Validate that the provided object is a coroutine
156
+ if not Type(self.__func).isCoroutine():
157
+ raise OrionisCoroutineException(
158
+ f"Expected a coroutine object, but got {type(self.__func).__name__}."
159
+ )
160
+
56
161
  # Attempt to get the currently running event loop
57
162
  try:
58
163
  loop = asyncio.get_running_loop()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: orionis
3
- Version: 0.532.0
3
+ Version: 0.534.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
@@ -13,6 +13,7 @@ orionis/console/base/scheduler_event_listener.py,sha256=tmdAMPzTmT8z0BcpyoIZwyTR
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
+ orionis/console/commands/make_listener.py,sha256=iPOMCc41njwUKurxSwatZUTjpp-bBLdO9nN-w2FJ4mI,1921
16
17
  orionis/console/commands/publisher.py,sha256=FUg-EUzK7LLXsla10ZUZro8V0Z5S-KjmsaSdRHSSGbA,21381
17
18
  orionis/console/commands/scheduler_list.py,sha256=A2N_mEXEJDHO8DX2TDrL1ROeeRhFSkWD3rCw64Hrf0o,4763
18
19
  orionis/console/commands/scheduler_work.py,sha256=mzSFes8Wl1gCf253tNYClij0abT5HlpW1QZVFrU5EXo,6445
@@ -28,7 +29,7 @@ orionis/console/contracts/schedule.py,sha256=17cfPYtLo-jqF8FxYOhh4epJZnxw5mMPuLG
28
29
  orionis/console/contracts/schedule_event_listener.py,sha256=7fQdPh6X_npfGpQW_2x81D7-5Pe40jIog9uDeEU0kro,4390
29
30
  orionis/console/contracts/scheduler.py,sha256=OW-a_YDDNPrenYT9z8Tv71VjyZ1aSzqzqhTBhTCZhGM,7698
30
31
  orionis/console/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
- orionis/console/core/reactor.py,sha256=AG2i38YRTHSRG-eO2lNCy9P-Qfll297EvxYHCT_wvV0,30512
32
+ orionis/console/core/reactor.py,sha256=cUYZwOyWjKai885peSaRRu3vM0IpWxskCNyl9IangHs,30626
32
33
  orionis/console/dumper/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
34
  orionis/console/dumper/dump.py,sha256=CATERiQ6XuIrKQsDaWcVxzTtlAJI9qLJX44fQxEX8ws,22443
34
35
  orionis/console/dumper/contracts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -83,7 +84,7 @@ orionis/console/request/cli_request.py,sha256=7-sgYmNUCipuHLVAwWLJiHv0cJCDmsM1Lu
83
84
  orionis/console/tasks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
84
85
  orionis/console/tasks/event.py,sha256=l4J-HEPaj1mxB_PYQMgG9dRHUe01wUag8fKLLnR2N2M,164395
85
86
  orionis/console/tasks/listener.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
86
- orionis/console/tasks/schedule.py,sha256=oezk-Z0L2hyB13c0KdWYnLL1mcQVzXBPmqgcbAapJ1w,82546
87
+ orionis/console/tasks/schedule.py,sha256=lou90LaOWw4_tw2ONrplVOEUI9A5esKspFL900EIUBI,82842
87
88
  orionis/container/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
88
89
  orionis/container/container.py,sha256=aF_b6lTUpG4YCo9yFJEzsntTdIzgMMXFW5LyWqAJVBQ,87987
89
90
  orionis/container/context/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -116,12 +117,12 @@ orionis/container/validators/is_subclass.py,sha256=4sBaGLoRs8nUhuWjlP0VJqyTwVHYq
116
117
  orionis/container/validators/is_valid_alias.py,sha256=4uAYcq8xov7jZbXnpKpjNkxcZtlTNnL5RRctVPMwJes,1424
117
118
  orionis/container/validators/lifetime.py,sha256=IQ43fDNrxYHMlZH2zlYDJnlkLO_eS4U7Fs3UJgQBidI,1844
118
119
  orionis/failure/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
119
- orionis/failure/catch.py,sha256=Tk2IE6lpt2n3jHn4--3U-QLyDrBIwUnNnKi7ZRmGnOg,3715
120
+ orionis/failure/catch.py,sha256=kmw2mbkGrFNw9s8jrr2NdJeGlbJVvXQQSlnjltwC4oM,6819
120
121
  orionis/failure/base/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
121
- orionis/failure/base/handler.py,sha256=L6jB2_2f8ZKBuys8o_iXTwMM_yjQHY7iXNF1Q3X8BGY,4661
122
+ orionis/failure/base/handler.py,sha256=a7fQJMWVjALwFiITRsMYg3s7WSO6zcbmq171iQRWy48,4867
122
123
  orionis/failure/contracts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
123
124
  orionis/failure/contracts/catch.py,sha256=e2wM1p6VxbvAjgWm-MwoM9p2ystSsyBu8Qnt6Ehr6Vc,1179
124
- orionis/failure/contracts/handler.py,sha256=4N9yMkMgdhtHbRGAyCtuTx3GmkPP74vhqdRLmwl-ckQ,2216
125
+ orionis/failure/contracts/handler.py,sha256=drNE8iu8RUHi3TgKn-lUEIfVsZeGsMTUecTZOfay19E,2240
125
126
  orionis/failure/entities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
126
127
  orionis/failure/entities/throwable.py,sha256=ogys062uhim5QMYU62ezlnumRAnYQlUf_vZvQY47S3U,1227
127
128
  orionis/foundation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -241,11 +242,11 @@ orionis/foundation/providers/scheduler_provider.py,sha256=1do4B09bU_6xbFHHVYYTGM
241
242
  orionis/foundation/providers/testing_provider.py,sha256=SrJRpdvcblx9WvX7x9Y3zc7OQfiTf7la0HAJrm2ESlE,3725
242
243
  orionis/foundation/providers/workers_provider.py,sha256=oa_2NIDH6UxZrtuGkkoo_zEoNIMGgJ46vg5CCgAm7wI,3926
243
244
  orionis/metadata/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
244
- orionis/metadata/framework.py,sha256=w2kA3Bwp6FiLOMcy7SJ7LlQTYV53dTXsZaNAfgJrq80,4109
245
+ orionis/metadata/framework.py,sha256=nuA2nefeGQ2B3VGS-om5qZigBmEBbAZwhTnm54CFJn0,4109
245
246
  orionis/metadata/package.py,sha256=k7Yriyp5aUcR-iR8SK2ec_lf0_Cyc-C7JczgXa-I67w,16039
246
247
  orionis/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
247
248
  orionis/services/asynchrony/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
248
- orionis/services/asynchrony/coroutines.py,sha256=8pSSgo-U9ncPLisJqIiu1BKRlWg6p6T6TFrVvh7UMVE,2594
249
+ orionis/services/asynchrony/coroutines.py,sha256=M1o4Al5dk8BR85CWHHUgymVQaD_6PgirenQMeITUoZQ,6863
249
250
  orionis/services/asynchrony/contracts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
250
251
  orionis/services/asynchrony/contracts/coroutines.py,sha256=rVy1HO58RFKliGeaf6GUC7mGtPZXimIhzkFHR1EWRoc,1082
251
252
  orionis/services/asynchrony/exceptions/__init__.py,sha256=COm6RhSiuwWqy3YcJ_0gVu6XHjn5P3zVaiUp5Pw_h48,99
@@ -419,7 +420,7 @@ orionis/test/validators/web_report.py,sha256=n9BfzOZz6aEiNTypXcwuWbFRG0OdHNSmCNu
419
420
  orionis/test/validators/workers.py,sha256=rWcdRexINNEmGaO7mnc1MKUxkHKxrTsVuHgbnIfJYgc,1206
420
421
  orionis/test/view/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
421
422
  orionis/test/view/render.py,sha256=f-zNhtKSg9R5Njqujbg2l2amAs2-mRVESneLIkWOZjU,4082
422
- orionis-0.532.0.dist-info/licenses/LICENCE,sha256=JhC-z_9mbpUrCfPjcl3DhDA8trNDMzb57cvRSam1avc,1463
423
+ orionis-0.534.0.dist-info/licenses/LICENCE,sha256=JhC-z_9mbpUrCfPjcl3DhDA8trNDMzb57cvRSam1avc,1463
423
424
  tests/container/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
424
425
  tests/container/context/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
425
426
  tests/container/context/test_manager.py,sha256=wOwXpl9rHNfTTexa9GBKYMwK0_-KSQPbI-AEyGNkmAE,1356
@@ -512,7 +513,7 @@ tests/metadata/test_metadata_framework.py,sha256=vOsp5owDMMoL5dtgiN9EC5Sdj8pemXV
512
513
  tests/metadata/test_metadata_package.py,sha256=AcD5rlkD5MeX2tJvQkIHeGeAl88k0tsF2wOU5v4JVN0,4894
513
514
  tests/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
514
515
  tests/services/asynchrony/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
515
- tests/services/asynchrony/test_services_asynchrony_coroutine.py,sha256=34Y0D6w2bVo_dm3oj0wzedMIJuSYieUG9Y2J1EQmZeo,3439
516
+ tests/services/asynchrony/test_services_asynchrony_coroutine.py,sha256=xWdH5CKrXQ8zGSFadEDN_7LoNEAcDWWIBdm2TVQPuLo,3445
516
517
  tests/services/environment/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
517
518
  tests/services/environment/test_services_environment.py,sha256=8gbF2vK238lracBipHCHgiYxThRnfLtN9pj--rFald0,9570
518
519
  tests/services/introspection/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -565,8 +566,8 @@ tests/testing/validators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZ
565
566
  tests/testing/validators/test_testing_validators.py,sha256=WPo5GxTP6xE-Dw3X1vZoqOMpb6HhokjNSbgDsDRDvy4,16588
566
567
  tests/testing/view/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
567
568
  tests/testing/view/test_render.py,sha256=tnnMBwS0iKUIbogLvu-7Rii50G6Koddp3XT4wgdFEYM,1050
568
- orionis-0.532.0.dist-info/METADATA,sha256=LmSfofSx_TzRb1Jptb5XE96fBBqmDlL4ylFo1aeHmQ0,4801
569
- orionis-0.532.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
570
- orionis-0.532.0.dist-info/top_level.txt,sha256=2bdoHgyGZhOtLAXS6Om8OCTmL24dUMC_L1quMe_ETbk,14
571
- orionis-0.532.0.dist-info/zip-safe,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
572
- orionis-0.532.0.dist-info/RECORD,,
569
+ orionis-0.534.0.dist-info/METADATA,sha256=R7FBw3jg3tfT8gujp8PmJLFqoTJK6oXImdwynE9zV9c,4801
570
+ orionis-0.534.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
571
+ orionis-0.534.0.dist-info/top_level.txt,sha256=2bdoHgyGZhOtLAXS6Om8OCTmL24dUMC_L1quMe_ETbk,14
572
+ orionis-0.534.0.dist-info/zip-safe,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
573
+ orionis-0.534.0.dist-info/RECORD,,
@@ -82,4 +82,4 @@ class TestServicesAsynchronyCoroutine(AsyncTestCase):
82
82
 
83
83
  # Assert that passing a non-coroutine raises OrionisCoroutineException
84
84
  with self.assertRaises(OrionisCoroutineException):
85
- Coroutine(sample_no_coroutine())
85
+ Coroutine(sample_no_coroutine()).run()