orionis 0.448.0__py3-none-any.whl → 0.450.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.
Files changed (34) hide show
  1. orionis/console/args/argument.py +174 -43
  2. orionis/console/args/parser.py +37 -3
  3. orionis/console/base/command.py +103 -48
  4. orionis/console/base/contracts/command.py +97 -40
  5. orionis/console/core/reactor.py +408 -14
  6. orionis/console/output/contracts/executor.py +93 -0
  7. orionis/console/output/executor.py +153 -0
  8. orionis/container/container.py +46 -22
  9. orionis/container/context/scope.py +1 -1
  10. orionis/foundation/application.py +6 -2
  11. orionis/foundation/providers/console_provider.py +35 -10
  12. orionis/foundation/providers/dumper_provider.py +42 -14
  13. orionis/foundation/providers/executor_provider.py +80 -0
  14. orionis/foundation/providers/inspirational_provider.py +43 -23
  15. orionis/foundation/providers/logger_provider.py +47 -8
  16. orionis/foundation/providers/progress_bar_provider.py +55 -10
  17. orionis/foundation/providers/testing_provider.py +75 -31
  18. orionis/foundation/providers/workers_provider.py +69 -11
  19. orionis/metadata/framework.py +1 -1
  20. orionis/support/facades/console.py +11 -3
  21. orionis/support/facades/dumper.py +10 -3
  22. orionis/support/facades/executor.py +24 -0
  23. orionis/support/facades/inspire.py +9 -6
  24. orionis/support/facades/logger.py +11 -4
  25. orionis/support/facades/progress_bar.py +9 -3
  26. orionis/support/facades/testing.py +10 -4
  27. orionis/support/facades/workers.py +8 -4
  28. orionis/test/kernel.py +2 -2
  29. {orionis-0.448.0.dist-info → orionis-0.450.0.dist-info}/METADATA +1 -1
  30. {orionis-0.448.0.dist-info → orionis-0.450.0.dist-info}/RECORD +34 -30
  31. {orionis-0.448.0.dist-info → orionis-0.450.0.dist-info}/WHEEL +0 -0
  32. {orionis-0.448.0.dist-info → orionis-0.450.0.dist-info}/licenses/LICENCE +0 -0
  33. {orionis-0.448.0.dist-info → orionis-0.450.0.dist-info}/top_level.txt +0 -0
  34. {orionis-0.448.0.dist-info → orionis-0.450.0.dist-info}/zip-safe +0 -0
@@ -1,68 +1,125 @@
1
1
 
2
2
  from abc import ABC, abstractmethod
3
- from typing import Any, Dict
3
+ from typing import Any, Dict, List
4
+
5
+ from orionis.console.args.argument import CLIArgument
4
6
 
5
7
  class IBaseCommand(ABC):
6
8
  """
7
- Abstract base contract for console commands in Orionis.
9
+ Abstract base contract for console commands in Orionis framework.
10
+
11
+ This abstract base class defines the standardized interface that all console
12
+ commands must implement within the Orionis framework. It provides a consistent
13
+ contract for command execution, argument handling, metadata storage, and console
14
+ output management.
8
15
 
9
- Inherits from ABC and defines the required interface for all console commands:
10
- - Stores parsed command-line arguments.
11
- - Requires implementation of main execution logic and argument accessors.
16
+ The class establishes the foundation for command-line interface functionality,
17
+ ensuring all commands follow a uniform pattern for registration, execution,
18
+ and user interaction while maintaining flexibility for specific command logic
19
+ implementation.
12
20
 
13
21
  Attributes
14
22
  ----------
15
- args : Dict[str, Any]
16
- Dictionary containing the parsed arguments for the command. Should be set by the command parser.
23
+ timestamps : bool, default=True
24
+ Controls whether timestamps are displayed in console output. When enabled,
25
+ all console messages will include timestamp prefixes for better debugging
26
+ and logging capabilities.
27
+ signature : str
28
+ The command signature string that defines the command name and expected
29
+ arguments format. Used for command registration in the console system
30
+ and automatic help text generation. Must follow the framework's signature
31
+ format conventions.
32
+ description : str
33
+ Human-readable description explaining the command's purpose and functionality.
34
+ This text is displayed in help documentation, command listings, and usage
35
+ instructions to assist users in understanding the command's capabilities.
36
+ _args : Dict[str, Any]
37
+ Dictionary containing parsed command-line arguments and options passed to
38
+ the command during execution. Populated automatically by the command parser
39
+ before the handle() method is called, providing structured access to all
40
+ user-provided input parameters.
41
+ arguments : List[CLIArgument]
42
+ List of CLIArgument instances defining the command's accepted arguments
43
+ and options. Used for argument parsing, validation, and help text generation.
44
+
45
+ Methods
46
+ -------
47
+ handle() -> None
48
+ Abstract method that must be implemented by all concrete command subclasses.
49
+ Contains the main execution logic specific to each command type and handles
50
+ argument processing, business logic execution, and output generation.
51
+
52
+ Notes
53
+ -----
54
+ - All concrete implementations must override the handle() method
55
+ - Command signatures should follow framework naming conventions
56
+ - Use self._args dictionary to access parsed command-line arguments
57
+ - Implement proper error handling and validation within command logic
58
+ - Follow single responsibility principle for maintainable command structure
59
+ - Utilize framework's console output methods for consistent user experience
60
+
61
+ See Also
62
+ --------
63
+ abc.ABC : Abstract base class functionality
64
+ typing.Dict : Type hints for argument dictionary structure
17
65
  """
18
66
 
19
- args: Dict[str, Any] = {}
67
+ # Enable timestamps in console output by default
68
+ timestamps: bool = True
20
69
 
21
- @abstractmethod
22
- def handle(self):
23
- """
24
- Main entry point for command execution.
70
+ # Command signature string for registration and help text generation
71
+ signature: str
25
72
 
26
- This method must be overridden in each subclass to define the specific logic of the command.
27
- Access parsed arguments via self.args and use console/output methods as needed.
73
+ # Human-readable description for documentation and help display
74
+ description: str
28
75
 
29
- Raises
30
- ------
31
- NotImplementedError
32
- Always raised in the base class. Subclasses must implement this method.
33
- """
34
- pass
76
+ # Dictionary to store parsed command-line arguments and options
77
+ _args: Dict[str, Any] = {}
78
+
79
+ # List of CLIArgument instances defining command arguments
80
+ arguments: List[CLIArgument] = []
35
81
 
36
82
  @abstractmethod
37
- def argument(self, key: str) -> Any:
83
+ def handle(self) -> None:
38
84
  """
39
- Retrieves the value of a specific argument by its key.
85
+ Execute the main logic of the console command.
86
+
87
+ This abstract method serves as the primary entry point for command execution
88
+ and must be implemented by all concrete command subclasses. The method contains
89
+ the core business logic specific to each command type and is responsible for
90
+ processing the parsed arguments stored in self.args and producing the desired
91
+ output or side effects.
40
92
 
41
- Parameters
42
- ----------
43
- key : str
44
- Name of the argument to retrieve.
93
+ The implementation should access parsed command-line arguments through the
94
+ self.args dictionary and utilize appropriate console output methods for
95
+ user feedback and result presentation. Error handling and resource cleanup
96
+ should also be managed within this method to ensure robust command execution.
45
97
 
46
98
  Returns
47
99
  -------
48
- Any or None
49
- Value associated with the key, or None if it does not exist or arguments are not set.
100
+ None
101
+ This method does not return any value. All command output, results,
102
+ error messages, and user feedback should be handled through console
103
+ output methods, file operations, database transactions, or other
104
+ side effects rather than return values.
50
105
 
51
106
  Raises
52
107
  ------
53
- ValueError
54
- If the key is not a string or if arguments are not a dictionary.
55
- """
56
- pass
108
+ NotImplementedError
109
+ Automatically raised when this method is called on the abstract base
110
+ class without a concrete implementation. All subclasses must override
111
+ this method with their specific command logic to avoid this exception.
57
112
 
58
- @abstractmethod
59
- def arguments(self) -> Dict[str, Any]:
113
+ Notes
114
+ -----
115
+ - Access command arguments and options via the self.args dictionary
116
+ - Use framework's console output methods for consistent user interaction
117
+ - Implement comprehensive error handling and input validation
118
+ - Ensure proper cleanup of resources (files, connections, etc.) if needed
119
+ - Follow the single responsibility principle for maintainable command logic
120
+ - Handle both success and failure scenarios appropriately
60
121
  """
61
- Returns all parsed arguments as a dictionary.
62
122
 
63
- Returns
64
- -------
65
- dict
66
- Dictionary containing all arguments received by the command. If no arguments, returns an empty dictionary.
67
- """
123
+ # Abstract method placeholder - concrete implementations must override this method
124
+ # Each subclass should replace this pass statement with specific command logic
68
125
  pass
@@ -1,28 +1,422 @@
1
+ import argparse
1
2
  import os
2
3
  from pathlib import Path
4
+ import time
5
+ from typing import List, Optional
6
+ from orionis.app import Orionis
7
+ from orionis.console.args.argument import CLIArgument
8
+ from orionis.console.base.command import BaseCommand
9
+ from orionis.console.base.contracts.command import IBaseCommand
10
+ from orionis.console.output.contracts.executor import IExecutor
11
+ from orionis.console.output.executor import Executor
12
+ from orionis.foundation.contracts.application import IApplication
3
13
  from orionis.services.introspection.modules.reflection import ReflectionModule
4
-
14
+ import re
5
15
 
6
16
  class Reactor:
7
17
 
8
18
  def __init__(
9
- self
19
+ self,
20
+ app: Optional[IApplication] = None
10
21
  ):
11
- self.__reactors = {}
12
- self.__command_path = str((Path.cwd() / 'app' / 'console' / 'commands').resolve())
13
- self.__loadCommands()
22
+ """
23
+ Initializes a new Reactor instance for command discovery and management.
24
+
25
+ The Reactor constructor sets up the command processing environment by establishing
26
+ the application context, determining the project root directory, and automatically
27
+ discovering and loading command classes from the console commands directory.
28
+ It maintains an internal registry of discovered commands for efficient lookup
29
+ and execution.
30
+
31
+ Parameters
32
+ ----------
33
+ app : Optional[IApplication], default None
34
+ The application instance to use for command processing. If None is provided,
35
+ a new Orionis application instance will be created automatically. The
36
+ application instance provides access to configuration, paths, and other
37
+ framework services required for command execution.
38
+
39
+ Returns
40
+ -------
41
+ None
42
+ This is a constructor method and does not return any value. The instance
43
+ is configured with the provided or default application and populated with
44
+ discovered commands.
45
+
46
+ Notes
47
+ -----
48
+ - Command discovery is performed automatically during initialization
49
+ - The current working directory is used as the project root for module resolution
50
+ - Commands are loaded from the path specified by app.path('console_commands')
51
+ - The internal command registry is initialized as an empty dictionary before loading
52
+ """
53
+
54
+ # Initialize the application instance, using provided app or creating new Orionis instance
55
+ self.__app = app or Orionis()
56
+
57
+ # Set the project root directory to current working directory for module path resolution
58
+ self.__root: str = str(Path.cwd())
59
+
60
+ # Initialize the internal command registry as an empty dictionary
61
+ self.__commands: dict = {}
62
+
63
+ # Automatically discover and load command classes from the console commands directory
64
+ self.__loadCommands(str(self.__app.path('console_commands')), self.__root)
65
+
66
+ # Initialize the executor for command output management
67
+ self.__executer: IExecutor = self.__app.make('x-orionis.console.output.executor')
68
+
69
+ def __loadCommands(self, commands_path: str, root_path: str) -> None:
70
+ """
71
+ Loads command classes from Python files in the specified commands directory.
14
72
 
15
- def __loadCommands(self):
73
+ This method recursively walks through the commands directory, imports Python modules,
74
+ and registers command classes that inherit from BaseCommand. It performs module path
75
+ sanitization to handle virtual environment paths and validates command structure
76
+ before registration.
16
77
 
17
- # Base path of the project
18
- root_path = str(Path.cwd())
78
+ Parameters
79
+ ----------
80
+ commands_path : str
81
+ The absolute path to the directory containing command modules to load.
82
+ root_path : str
83
+ The root path of the project, used for module path normalization.
84
+
85
+ Returns
86
+ -------
87
+ None
88
+ This method does not return any value. Command classes are registered
89
+ internally in the reactor's command registry.
90
+
91
+ Notes
92
+ -----
93
+ - Only Python files (.py extension) are processed
94
+ - Virtual environment paths are automatically filtered out during module resolution
95
+ - Command classes must inherit from BaseCommand to be registered
96
+ - Each discovered command class undergoes structure validation via __ensureStructure
97
+ """
19
98
 
20
99
  # Iterate through the command path and load command modules
21
- for current_directory, _, files in os.walk(self.__command_path):
100
+ for current_directory, _, files in os.walk(commands_path):
22
101
  for file in files:
102
+
103
+ # Only process Python files
23
104
  if file.endswith('.py'):
24
- pre_module = current_directory.replace(root_path, '').replace(os.sep, '.').lstrip('.')
25
- file_name = file[:-3]
26
- print(f"{pre_module}.{file_name}")
27
- rf_module = ReflectionModule(f"{pre_module}.{file_name}")
28
- print(rf_module.getClasses())
105
+
106
+ # Sanitize the module path by converting filesystem path to Python module notation
107
+ pre_module = current_directory.replace(root_path, '')\
108
+ .replace(os.sep, '.')\
109
+ .lstrip('.')
110
+
111
+ # Remove virtual environment paths using regex (Windows, Linux, macOS)
112
+ # Windows: venv\Lib\site-packages or venv\lib\site-packages
113
+ # Linux/macOS: venv/lib/python*/site-packages
114
+ pre_module = re.sub(r'[^.]*\.(?:Lib|lib)\.(?:python[^.]*\.)?site-packages\.?', '', pre_module)
115
+
116
+ # Remove any remaining .venv or venv patterns from the module path
117
+ pre_module = re.sub(r'\.?v?env\.?', '', pre_module)
118
+
119
+ # Clean up any double dots or leading/trailing dots that may have been created
120
+ pre_module = re.sub(r'\.+', '.', pre_module).strip('.')
121
+
122
+ # Skip if module name is empty after cleaning (invalid module path)
123
+ if not pre_module:
124
+ continue
125
+
126
+ # Create the reflection module path by combining sanitized path with filename
127
+ rf_module = ReflectionModule(f"{pre_module}.{file[:-3]}")
128
+
129
+ # Iterate through all classes found in the current module
130
+ for name, obj in rf_module.getClasses().items():
131
+
132
+ # Check if the class is a valid command class (inherits from BaseCommand but is not BaseCommand itself)
133
+ if issubclass(obj, BaseCommand) and obj is not BaseCommand and obj is not IBaseCommand:
134
+
135
+ # Validate the command class structure and register it
136
+ timestamp = self.__ensureTimestamps(obj)
137
+ signature = self.__ensureSignature(obj)
138
+ description = self.__ensureDescription(obj)
139
+ args = self.__ensureArguments(obj)
140
+
141
+ # Add the command to the internal registry
142
+ self.__commands[signature] = {
143
+ "class": obj,
144
+ "timestamps": timestamp,
145
+ "signature": signature,
146
+ "description": description,
147
+ "args": args
148
+ }
149
+
150
+ def __ensureTimestamps(self, obj: IBaseCommand) -> bool:
151
+ """
152
+ Validates that a command class has a properly formatted timestamps attribute.
153
+
154
+ This method ensures that the command class contains a 'timestamps' attribute
155
+ that is a boolean value, indicating whether timestamps should be included in
156
+ console output messages.
157
+
158
+ Parameters
159
+ ----------
160
+ obj : IBaseCommand
161
+ The command class instance to validate.
162
+
163
+ Returns
164
+ -------
165
+ Reactor
166
+ Returns self to enable method chaining for additional validation calls.
167
+
168
+ Raises
169
+ ------
170
+ ValueError
171
+ If the command class lacks a 'timestamps' attribute or if the attribute is not a boolean.
172
+ """
173
+
174
+ # Check if the command class has a timestamps attribute
175
+ if not hasattr(obj, 'timestamps'):
176
+ return False
177
+
178
+ # Ensure the timestamps attribute is a boolean type
179
+ if not isinstance(obj.timestamps, bool):
180
+ raise TypeError(f"Command class {obj.__name__} 'timestamps' must be a boolean.")
181
+
182
+ # Return timestamps value
183
+ return obj.timestamps
184
+
185
+ def __ensureSignature(self, obj: IBaseCommand) -> str:
186
+ """
187
+ Validates that a command class has a properly formatted signature attribute.
188
+
189
+ This method ensures that the command class contains a 'signature' attribute
190
+ that follows the required naming conventions for command identification.
191
+ The signature must be a non-empty string containing only alphanumeric
192
+ characters, underscores, and colons, with specific placement rules.
193
+
194
+ Parameters
195
+ ----------
196
+ obj : IBaseCommand
197
+ The command class instance to validate.
198
+
199
+ Returns
200
+ -------
201
+ Reactor
202
+ Returns self to enable method chaining.
203
+
204
+ Raises
205
+ ------
206
+ ValueError
207
+ If the command class lacks a 'signature' attribute, if the signature
208
+ is an empty string, or if the signature doesn't match the required pattern.
209
+ TypeError
210
+ If the 'signature' attribute is not a string.
211
+ """
212
+
213
+ # Check if the command class has a signature attribute
214
+ if not hasattr(obj, 'signature'):
215
+ raise ValueError(f"Command class {obj.__name__} must have a 'signature' attribute.")
216
+
217
+ # Ensure the signature attribute is a string type
218
+ if not isinstance(obj.signature, str):
219
+ raise TypeError(f"Command class {obj.__name__} 'signature' must be a string.")
220
+
221
+ # Validate that the signature is not empty after stripping whitespace
222
+ if obj.signature.strip() == '':
223
+ raise ValueError(f"Command class {obj.__name__} 'signature' cannot be an empty string.")
224
+
225
+ # Define the regex pattern for valid signature format
226
+ # Pattern allows: alphanumeric chars, underscores, colons
227
+ # Cannot start/end with underscore or colon, cannot start with number
228
+ pattern = r'^[a-zA-Z][a-zA-Z0-9_:]*[a-zA-Z0-9]$|^[a-zA-Z]$'
229
+
230
+ # Validate the signature against the required pattern
231
+ if not re.match(pattern, obj.signature):
232
+ raise ValueError(f"Command class {obj.__name__} 'signature' must contain only alphanumeric characters, underscores (_) and colons (:), cannot start or end with underscore or colon, and cannot start with a number.")
233
+
234
+ # Return signature
235
+ return obj.signature.strip()
236
+
237
+ def __ensureDescription(self, obj: IBaseCommand) -> str:
238
+ """
239
+ Validates that a command class has a properly formatted description attribute.
240
+
241
+ This method ensures that the command class contains a 'description' attribute
242
+ that provides meaningful documentation for the command. The description must
243
+ be a non-empty string that can be used for help documentation and command
244
+ listing purposes.
245
+
246
+ Parameters
247
+ ----------
248
+ obj : IBaseCommand
249
+ The command class instance to validate.
250
+
251
+ Returns
252
+ -------
253
+ Reactor
254
+ Returns self to enable method chaining for additional validation calls.
255
+
256
+ Raises
257
+ ------
258
+ ValueError
259
+ If the command class lacks a 'description' attribute or if the description
260
+ is an empty string after stripping whitespace.
261
+ TypeError
262
+ If the 'description' attribute is not a string type.
263
+ """
264
+
265
+ # Check if the command class has a description attribute
266
+ if not hasattr(obj, 'description'):
267
+ raise ValueError(f"Command class {obj.__name__} must have a 'description' attribute.")
268
+
269
+ # Ensure the description attribute is a string type
270
+ if not isinstance(obj.description, str):
271
+ raise TypeError(f"Command class {obj.__name__} 'description' must be a string.")
272
+
273
+ # Validate that the description is not empty after stripping whitespace
274
+ if obj.description.strip() == '':
275
+ raise ValueError(f"Command class {obj.__name__} 'description' cannot be an empty string.")
276
+
277
+ # Return description
278
+ return obj.description.strip()
279
+
280
+ def __ensureArguments(self, obj: IBaseCommand) -> Optional[argparse.ArgumentParser]:
281
+ """
282
+ Validates and processes command arguments for a command class.
283
+
284
+ This method ensures that the command class has properly formatted arguments
285
+ and creates an ArgumentParser instance configured with those arguments.
286
+
287
+ Parameters
288
+ ----------
289
+ obj : IBaseCommand
290
+ The command class instance to validate.
291
+
292
+ Returns
293
+ -------
294
+ Optional[argparse.ArgumentParser]
295
+ An ArgumentParser instance configured with the command's arguments,
296
+ or None if the command has no arguments.
297
+
298
+ Raises
299
+ ------
300
+ TypeError
301
+ If the 'arguments' attribute is not a list or contains non-CLIArgument instances.
302
+ """
303
+
304
+ # Check if the command class has an arguments attribute
305
+ if not hasattr(obj, 'arguments'):
306
+ return None
307
+
308
+ # Ensure the arguments attribute is a list type
309
+ if not isinstance(obj.arguments, list):
310
+ raise TypeError(f"Command class {obj.__name__} 'arguments' must be a list.")
311
+
312
+ # If arguments is empty, return None
313
+ if len(obj.arguments) == 0:
314
+ return None
315
+
316
+ # Validate that all items in the arguments list are CLIArgument instances
317
+ for index, value in enumerate(obj.arguments):
318
+ if not isinstance(value, CLIArgument):
319
+ raise TypeError(f"Command class {obj.__name__} 'arguments' must contain only CLIArgument instances, found '{type(value).__name__}' at index {index}.")
320
+
321
+ # Build the arguments dictionary from the CLIArgument instances
322
+ required_args: List[CLIArgument] = obj.arguments
323
+
324
+ # Create an ArgumentParser instance to handle the command arguments
325
+ arg_parser = argparse.ArgumentParser(
326
+ description=f"{obj.signature} - {obj.description}",
327
+ formatter_class=argparse.RawDescriptionHelpFormatter,
328
+ add_help=True,
329
+ allow_abbrev=False
330
+ )
331
+ for arg in required_args:
332
+ arg.addToParser(arg_parser)
333
+
334
+ # Return the configured ArgumentParser
335
+ return arg_parser
336
+
337
+ def call(
338
+ self,
339
+ signature: str,
340
+ args: Optional[List[str]] = None
341
+ ):
342
+ """
343
+ Executes a command by its signature with optional command-line arguments.
344
+
345
+ This method retrieves a registered command by its signature, validates any provided
346
+ arguments against the command's argument parser, and executes the command's handle
347
+ method with the parsed arguments and application context.
348
+
349
+ Parameters
350
+ ----------
351
+ signature : str
352
+ The unique signature identifier of the command to execute.
353
+ args : Optional[List[str]], default None
354
+ Command-line arguments to pass to the command. If None, no arguments are provided.
355
+
356
+ Returns
357
+ -------
358
+ None
359
+ This method does not return any value. The command's handle method is called
360
+ directly for execution.
361
+
362
+ Raises
363
+ ------
364
+ ValueError
365
+ If the command with the specified signature is not found in the registry.
366
+ SystemExit
367
+ If argument parsing fails due to invalid arguments provided.
368
+ """
369
+
370
+ # Retrieve the command from the registry
371
+ command: dict = self.__commands.get(signature)
372
+ if command is None:
373
+ raise ValueError(f"Command '{signature}' not found.")
374
+
375
+ # Start execution timer
376
+ start_time = time.perf_counter()
377
+
378
+ # Get command details
379
+ command_class = command.get("class")
380
+ arg_parser: Optional[argparse.ArgumentParser] = command.get("args")
381
+ timestamps: bool = command.get("timestamps")
382
+
383
+ # Initialize parsed arguments
384
+ parsed_args = None
385
+
386
+ # If the command has arguments and args were provided, validate them
387
+ if arg_parser is not None and isinstance(arg_parser, argparse.ArgumentParser):
388
+ if args is None:
389
+ args = []
390
+ try:
391
+ # Parse the provided arguments using the command's ArgumentParser
392
+ parsed_args = arg_parser.parse_args(args)
393
+ except SystemExit:
394
+ # Re-raise SystemExit to allow argparse to handle help/error messages
395
+ raise
396
+
397
+ # Log the command execution start with RUNNING state
398
+ if timestamps:
399
+ self.__executer.running(program=signature)
400
+
401
+ try:
402
+
403
+ # Create an instance of the command class and execute it
404
+ command_instance: IBaseCommand = command_class()
405
+ command_instance._args = vars(parsed_args) if parsed_args else {}
406
+ output = command_instance.handle(self.__app.make('x-orionis.services.inspirational.inspire'))
407
+
408
+ # Log the command execution completion with DONE state
409
+ if timestamps:
410
+ elapsed_time = round(time.perf_counter() - start_time, 2)
411
+ self.__executer.done(program=signature, time=f"{elapsed_time}s")
412
+
413
+ # If the command has a return value or output, return it
414
+ if output is not None:
415
+ return output
416
+
417
+ except Exception as e:
418
+
419
+ # Log the command execution failure with ERROR state
420
+ if timestamps:
421
+ elapsed_time = round(time.perf_counter() - start_time, 2)
422
+ self.__executer.fail(program=signature, time=f"{elapsed_time}s")