orionis 0.44.0__py3-none-any.whl → 0.47.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (172) hide show
  1. orionis/cli_manager.py +16 -30
  2. orionis/contracts/bootstrap/i_command_bootstrapper.py +59 -0
  3. orionis/contracts/bootstrap/i_config_bootstrapper.py +112 -0
  4. orionis/contracts/bootstrap/i_environment_bootstrapper.py +33 -0
  5. orionis/{luminate/contracts/console/base/base_command_interface.py → contracts/console/base/i_command.py} +0 -1
  6. orionis/contracts/facades/config/i_config_facade.py +37 -0
  7. orionis/contracts/facades/environment/i_environment_facade.py +40 -0
  8. orionis/{luminate/contracts/facades/paths_interface.py → contracts/facades/files/i_path_facade.py} +27 -20
  9. orionis/contracts/facades/log/i_log_facade.py +83 -0
  10. orionis/{luminate/contracts/facades/tests_interface.py → contracts/facades/tests/i_tests_facade.py} +3 -6
  11. orionis/{luminate/contracts/console/scripts/management_interface.py → contracts/installer/i_installer_manager.py} +19 -16
  12. orionis/contracts/installer/i_installer_output.py +101 -0
  13. orionis/contracts/installer/i_installer_setup.py +59 -0
  14. orionis/contracts/providers/i_service_provider.py +27 -0
  15. orionis/contracts/services/config/i_config_service.py +37 -0
  16. orionis/contracts/services/files/i_path_service.py +31 -0
  17. orionis/contracts/services/log/i_log_service.py +89 -0
  18. orionis/{luminate/facades/environment.py → contracts/support/i_environment.py} +25 -32
  19. orionis/{luminate/contracts/tools/exception_to_dict_interface.py → contracts/support/i_exception_to_dict.py} +0 -9
  20. orionis/{luminate/contracts/tools/reflection_interface.py → contracts/support/i_reflection.py} +17 -8
  21. orionis/{luminate/contracts/tools/std_interface.py → contracts/support/i_std.py} +3 -16
  22. orionis/framework.py +1 -1
  23. orionis/{luminate/console/scripts/management.py → installer/installer_manager.py} +22 -16
  24. orionis/{luminate/installer/output.py → installer/installer_output.py} +16 -10
  25. orionis/{luminate/installer/setup.py → installer/installer_setup.py} +13 -25
  26. orionis/luminate/app.py +15 -11
  27. orionis/luminate/bootstrap/command_bootstrapper.py +150 -0
  28. orionis/luminate/bootstrap/config_bootstrapper.py +168 -0
  29. orionis/luminate/bootstrap/environment_bootstrapper.py +67 -0
  30. orionis/luminate/config/helpers.py +20 -0
  31. orionis/luminate/console/base/command.py +1 -3
  32. orionis/luminate/console/command_filter.py +2 -2
  33. orionis/luminate/console/commands/cache_clear.py +17 -5
  34. orionis/luminate/console/commands/help.py +19 -13
  35. orionis/luminate/console/commands/schedule_work.py +6 -3
  36. orionis/luminate/console/commands/tests.py +1 -6
  37. orionis/luminate/console/commands/version.py +0 -5
  38. orionis/luminate/console/exceptions/cli_exception.py +1 -4
  39. orionis/luminate/console/kernel.py +2 -2
  40. orionis/luminate/console/output/console.py +2 -2
  41. orionis/luminate/console/output/executor.py +1 -1
  42. orionis/luminate/console/output/progress_bar.py +1 -1
  43. orionis/luminate/console/parser.py +2 -2
  44. orionis/luminate/console/runner.py +127 -126
  45. orionis/luminate/console/tasks/scheduler.py +8 -7
  46. orionis/luminate/container/container.py +1 -1
  47. orionis/luminate/container/types.py +38 -12
  48. orionis/luminate/facades/config/config_facade.py +43 -0
  49. orionis/luminate/facades/environment/environment_facade.py +68 -0
  50. orionis/luminate/facades/{paths.py → files/path_facade.py} +50 -6
  51. orionis/luminate/facades/log/__init__.py +0 -0
  52. orionis/luminate/facades/log/log_facade.py +95 -0
  53. orionis/luminate/facades/tests/__init__.py +0 -0
  54. orionis/luminate/facades/{tests.py → tests/tests_facade.py} +1 -1
  55. orionis/luminate/patterns/__init__.py +0 -0
  56. orionis/luminate/pipelines/cli_pipeline.py +122 -116
  57. orionis/luminate/providers/__init__.py +0 -0
  58. orionis/luminate/providers/config/__init__.py +0 -0
  59. orionis/luminate/providers/config/config_service_provider.py +26 -0
  60. orionis/luminate/providers/files/__init__.py +0 -0
  61. orionis/luminate/providers/files/path_service_provider.py +26 -0
  62. orionis/luminate/providers/log/__init__.py +0 -0
  63. orionis/luminate/providers/log/log_service_provider.py +26 -0
  64. orionis/luminate/services/__init__.py +0 -0
  65. orionis/luminate/services/config/__init__.py +0 -0
  66. orionis/luminate/services/config/config_service.py +72 -0
  67. orionis/luminate/services/files/__init__.py +0 -0
  68. orionis/luminate/services/files/path_service.py +71 -0
  69. orionis/luminate/services/log/__init__.py +0 -0
  70. orionis/luminate/services/log/log_service.py +159 -0
  71. orionis/luminate/{config → support}/environment.py +21 -32
  72. orionis/luminate/{tools → support}/exception_to_dict.py +1 -1
  73. orionis/luminate/{tools → support}/reflection.py +1 -1
  74. orionis/luminate/{tools → support}/std.py +1 -1
  75. orionis/luminate/test/unit_test.py +1 -2
  76. {orionis-0.44.0.dist-info → orionis-0.47.0.dist-info}/METADATA +1 -1
  77. orionis-0.47.0.dist-info/RECORD +157 -0
  78. tests/tools/test_reflection.py +1 -1
  79. orionis/luminate/bootstrap/commands/bootstrapper.py +0 -101
  80. orionis/luminate/bootstrap/commands/register.py +0 -92
  81. orionis/luminate/bootstrap/config/bootstrapper.py +0 -79
  82. orionis/luminate/bootstrap/config/parser.py +0 -53
  83. orionis/luminate/bootstrap/config/register.py +0 -76
  84. orionis/luminate/cache/app/config.py +0 -91
  85. orionis/luminate/cache/console/commands.py +0 -97
  86. orionis/luminate/console/cache.py +0 -87
  87. orionis/luminate/console/command.py +0 -40
  88. orionis/luminate/contracts/bootstrap/commands/bootstrapper_interface.py +0 -44
  89. orionis/luminate/contracts/bootstrap/commands/register_interface.py +0 -33
  90. orionis/luminate/contracts/bootstrap/config/bootstrapper_interface.py +0 -40
  91. orionis/luminate/contracts/bootstrap/config/parser_interface.py +0 -46
  92. orionis/luminate/contracts/bootstrap/config/register_interface.py +0 -47
  93. orionis/luminate/contracts/cache/app/config_interface.py +0 -76
  94. orionis/luminate/contracts/cache/console/commands_interface.py +0 -78
  95. orionis/luminate/contracts/config/environment_interface.py +0 -64
  96. orionis/luminate/contracts/console/command_interface.py +0 -36
  97. orionis/luminate/contracts/console/runner_interface.py +0 -50
  98. orionis/luminate/contracts/facades/env_interface.py +0 -64
  99. orionis/luminate/contracts/facades/log_interface.py +0 -48
  100. orionis/luminate/contracts/files/paths_interface.py +0 -29
  101. orionis/luminate/contracts/installer/output_interface.py +0 -125
  102. orionis/luminate/contracts/installer/setup_interface.py +0 -29
  103. orionis/luminate/contracts/installer/upgrade_interface.py +0 -24
  104. orionis/luminate/contracts/log/logger_interface.py +0 -33
  105. orionis/luminate/contracts/pipelines/cli_pipeline_interface.py +0 -84
  106. orionis/luminate/contracts/publisher/pypi_publisher_interface.py +0 -36
  107. orionis/luminate/contracts/test/unit_test_interface.py +0 -51
  108. orionis/luminate/facades/config.py +0 -10
  109. orionis/luminate/facades/log.py +0 -56
  110. orionis/luminate/files/paths.py +0 -56
  111. orionis/luminate/installer/upgrade.py +0 -42
  112. orionis/luminate/log/logger.py +0 -116
  113. orionis/luminate/publisher/pypi.py +0 -215
  114. orionis-0.44.0.dist-info/RECORD +0 -156
  115. /orionis/{luminate/bootstrap/config → contracts}/__init__.py +0 -0
  116. /orionis/{luminate/cache → contracts/config}/__init__.py +0 -0
  117. /orionis/{luminate/contracts/config/config_interface.py → contracts/config/i_config.py} +0 -0
  118. /orionis/{luminate/cache/app → contracts/console}/__init__.py +0 -0
  119. /orionis/{luminate/cache/console → contracts/console/base}/__init__.py +0 -0
  120. /orionis/{luminate/contracts/console/command_filter_interface.py → contracts/console/i_command_filter.py} +0 -0
  121. /orionis/{luminate/contracts/console/kernel_interface.py → contracts/console/i_kernel.py} +0 -0
  122. /orionis/{luminate/contracts/console/parser_interface.py → contracts/console/i_parser.py} +0 -0
  123. /orionis/{luminate/contracts/console/task_manager_interface.py → contracts/console/i_task_manager.py} +0 -0
  124. /orionis/{luminate/config/dataclass → contracts/console/output}/__init__.py +0 -0
  125. /orionis/{luminate/contracts/console/output/console_interface.py → contracts/console/output/i_console.py} +0 -0
  126. /orionis/{luminate/contracts/console/output/executor_interface.py → contracts/console/output/i_executor.py} +0 -0
  127. /orionis/{luminate/contracts/console/output/progress_bar_interface.py → contracts/console/output/i_progress_bar.py} +0 -0
  128. /orionis/{luminate/console/scripts → contracts/console/tasks}/__init__.py +0 -0
  129. /orionis/{luminate/contracts/console/tasks/schedule_interface.py → contracts/console/tasks/i_schedule.py} +0 -0
  130. /orionis/{luminate/contracts/container/container_interface.py → contracts/container/i_container.py} +0 -0
  131. /orionis/{luminate/contracts/container/types_interface.py → contracts/container/i_types.py} +0 -0
  132. /orionis/{luminate/contracts → contracts/facades}/__init__.py +0 -0
  133. /orionis/{luminate/contracts/cache → contracts/facades/config}/__init__.py +0 -0
  134. /orionis/{luminate/contracts/config → contracts/facades/environment}/__init__.py +0 -0
  135. /orionis/{luminate/contracts/console → contracts/facades/files}/__init__.py +0 -0
  136. /orionis/{luminate/contracts/facades → contracts/facades/log}/__init__.py +0 -0
  137. /orionis/{luminate/contracts/files → contracts/facades/tests}/__init__.py +0 -0
  138. /orionis/{luminate/contracts → contracts}/installer/__init__.py +0 -0
  139. /orionis/{luminate/contracts/log → contracts/providers}/__init__.py +0 -0
  140. /orionis/{luminate/contracts/publisher → contracts/services}/__init__.py +0 -0
  141. /orionis/{luminate/contracts/test → contracts/services/config}/__init__.py +0 -0
  142. /orionis/{luminate/contracts/tools → contracts/services/files}/__init__.py +0 -0
  143. /orionis/{luminate/files → contracts/services/log}/__init__.py +0 -0
  144. /orionis/{luminate/installer → installer}/__init__.py +0 -0
  145. /orionis/luminate/bootstrap/{cli_exception.py → exception_bootstrapper.py} +0 -0
  146. /orionis/luminate/config/{dataclass/app.py → app.py} +0 -0
  147. /orionis/luminate/config/{dataclass/auth.py → auth.py} +0 -0
  148. /orionis/luminate/config/{dataclass/cache.py → cache.py} +0 -0
  149. /orionis/luminate/config/{dataclass/cors.py → cors.py} +0 -0
  150. /orionis/luminate/config/{dataclass/database.py → database.py} +0 -0
  151. /orionis/luminate/config/{dataclass/filesystems.py → filesystems.py} +0 -0
  152. /orionis/luminate/config/{dataclass/logging.py → logging.py} +0 -0
  153. /orionis/luminate/config/{dataclass/mail.py → mail.py} +0 -0
  154. /orionis/luminate/config/{dataclass/queue.py → queue.py} +0 -0
  155. /orionis/luminate/config/{dataclass/session.py → session.py} +0 -0
  156. /orionis/luminate/{log → facades/config}/__init__.py +0 -0
  157. /orionis/luminate/{publisher → facades/environment}/__init__.py +0 -0
  158. /orionis/luminate/{tools → facades/files}/__init__.py +0 -0
  159. /orionis/luminate/{tools → support}/dot_dict.py +0 -0
  160. /orionis/{luminate/installer → static/ascii}/icon.ascii +0 -0
  161. /orionis/{luminate/installer → static/ascii}/info.ascii +0 -0
  162. /orionis/{luminate/static → static}/bg/galaxy.jpg +0 -0
  163. /orionis/{luminate/static → static}/favicon/OrionisFrameworkFavicon.png +0 -0
  164. /orionis/{luminate/static → static}/logos/OrionisFramework.jpg +0 -0
  165. /orionis/{luminate/static → static}/logos/OrionisFramework.png +0 -0
  166. /orionis/{luminate/static → static}/logos/OrionisFramework.psd +0 -0
  167. /orionis/{luminate/static → static}/logos/OrionisFramework2.png +0 -0
  168. /orionis/{luminate/static → static}/logos/OrionisFramework3.png +0 -0
  169. {orionis-0.44.0.dist-info → orionis-0.47.0.dist-info}/LICENCE +0 -0
  170. {orionis-0.44.0.dist-info → orionis-0.47.0.dist-info}/WHEEL +0 -0
  171. {orionis-0.44.0.dist-info → orionis-0.47.0.dist-info}/entry_points.txt +0 -0
  172. {orionis-0.44.0.dist-info → orionis-0.47.0.dist-info}/top_level.txt +0 -0
@@ -1,10 +1,11 @@
1
1
  import os
2
2
  import sys
3
3
  import datetime
4
+ import traceback
5
+ from orionis.contracts.installer.i_installer_output import IInstallerOutput
4
6
  from orionis.framework import NAME, VERSION, DOCS
5
- from orionis.luminate.contracts.installer.output_interface import IOutput
6
7
 
7
- class Output(IOutput):
8
+ class InstallerOutput(IInstallerOutput):
8
9
  """
9
10
  Class for displaying various types of messages to the console, including:
10
11
  - Welcome messages
@@ -60,7 +61,7 @@ class Output(IOutput):
60
61
  try:
61
62
  # Try loading and printing ASCII art from the file
62
63
  dir_path = os.path.dirname(__file__)
63
- path = os.path.join(dir_path, 'icon.ascii')
64
+ path = os.path.join(dir_path, '..', 'static', 'ascii', 'icon.ascii')
64
65
  with open(path, 'r', encoding='utf-8') as file:
65
66
  content = file.read()
66
67
 
@@ -95,7 +96,7 @@ class Output(IOutput):
95
96
  try:
96
97
  # Try loading and printing ASCII art from the file
97
98
  dir_path = os.path.dirname(__file__)
98
- path = os.path.join(dir_path, 'info.ascii')
99
+ path = os.path.join(dir_path, '..', 'static', 'ascii', 'info.ascii')
99
100
  with open(path, 'r', encoding='utf-8') as file:
100
101
  content = file.read()
101
102
 
@@ -132,7 +133,7 @@ class Output(IOutput):
132
133
  Displays the starting message when the installation begins.
133
134
  This includes a welcoming message and the ASCII art.
134
135
  """
135
- Output.asciiIco()
136
+ InstallerOutput.asciiIco()
136
137
  print(f'\u001b[32m{NAME}\u001b[0m: Thank you for using the framework!')
137
138
 
138
139
  @staticmethod
@@ -154,7 +155,7 @@ class Output(IOutput):
154
155
  message : str, optional
155
156
  The message to display. Defaults to an empty string.
156
157
  """
157
- Output._print("INFO", message, "44")
158
+ InstallerOutput._print("INFO", message, "44")
158
159
 
159
160
  @staticmethod
160
161
  def fail(message: str = ''):
@@ -166,10 +167,10 @@ class Output(IOutput):
166
167
  message : str, optional
167
168
  The message to display. Defaults to an empty string.
168
169
  """
169
- Output._print("FAIL", message, "43")
170
+ InstallerOutput._print("FAIL", message, "43")
170
171
 
171
172
  @staticmethod
172
- def error(message: str = ''):
173
+ def error(message: str = '', e = None):
173
174
  """
174
175
  Displays an error message to the console and terminates the program.
175
176
 
@@ -183,6 +184,11 @@ class Output(IOutput):
183
184
  SystemExit
184
185
  Terminates the program with a non-zero exit code, indicating an error occurred.
185
186
  """
186
- Output._print("ERROR", message, "41")
187
- print("\n")
187
+
188
+ InstallerOutput._print("ERROR", message, "41")
189
+
190
+ if isinstance(e, BaseException):
191
+ print("\n--- Traceback (most recent call last) ---")
192
+ traceback.print_exc()
193
+
188
194
  sys.exit(1)
@@ -2,14 +2,12 @@ import re
2
2
  import os
3
3
  import sys
4
4
  import shutil
5
- import tempfile
6
5
  import subprocess
7
6
  from unicodedata import normalize
8
7
  from orionis.framework import SKELETON, DOCS
9
- from orionis.luminate.installer.output import Output
10
- from orionis.luminate.contracts.installer.setup_interface import ISetup
8
+ from orionis.installer.installer_output import InstallerOutput
11
9
 
12
- class Setup(ISetup):
10
+ class InstallerSetup:
13
11
  """
14
12
  A class to initialize a Orionis project by performing the following setup actions:
15
13
  1. Sanitize the folder name.
@@ -30,19 +28,19 @@ class Setup(ISetup):
30
28
  Attributes
31
29
  ----------
32
30
  output : Output
33
- An instance of Output used for logging information.
31
+ An instance of Output used for console information.
34
32
  name_app_folder : str
35
33
  The sanitized folder name for the application.
36
34
  """
37
35
 
38
- def __init__(self, name: str = 'example-app', output = Output):
36
+ def __init__(self, name: str = 'example-app', output = InstallerOutput):
39
37
  """
40
38
  Initialize OrionislInit class.
41
39
 
42
40
  Parameters
43
41
  ----------
44
- output : Output
45
- An instance of Output to handle logging.
42
+ output : InstallerOutput
43
+ An instance of InstallerOutput.
46
44
  name_app : str, optional
47
45
  Name of the application. If not provided, defaults to "example-app".
48
46
  """
@@ -79,7 +77,7 @@ class Setup(ISetup):
79
77
  If the sanitized folder name is empty or contains invalid characters.
80
78
  """
81
79
  if not name:
82
- self.output.error("Folder name cannot be empty.")
80
+ raise ValueError("Folder name cannot be empty.")
83
81
 
84
82
  # Strip leading and trailing whitespace
85
83
  name = name.strip()
@@ -101,10 +99,10 @@ class Setup(ISetup):
101
99
 
102
100
  # Validate against allowed characters
103
101
  if not re.match(r'^[a-z0-9_-]+$', name):
104
- self.output.error("The folder name can only contain letters, numbers, underscores, and hyphens.")
102
+ raise ValueError("The folder name can only contain letters, numbers, underscores, and hyphens.")
105
103
 
106
104
  if not name:
107
- self.output.error("The sanitized folder name is empty after processing.")
105
+ raise ValueError("The sanitized folder name is empty after processing.")
108
106
 
109
107
  return name
110
108
 
@@ -130,7 +128,7 @@ class Setup(ISetup):
130
128
  try:
131
129
  # Validate Folder
132
130
  if os.path.exists(self.name_app_folder) and os.path.isdir(self.name_app_folder):
133
- self.output.error(f"The folder '{self.name_app_folder}' already exists.")
131
+ raise ValueError(f"The folder '{self.name_app_folder}' already exists.")
134
132
 
135
133
  # Clone the repository
136
134
  self.output.info(f"Cloning the repository into '{self.name_app_folder}'... (Getting Latest Version)")
@@ -152,7 +150,7 @@ class Setup(ISetup):
152
150
 
153
151
  # Check if requirements.txt exists
154
152
  if not os.path.exists("requirements.txt"):
155
- self.output.error(f"'requirements.txt' not found. Please visit the Orionis Docs for more details: {DOCS}")
153
+ raise ValueError(f"'requirements.txt' not found. Please visit the Orionis Docs for more details: {DOCS}")
156
154
 
157
155
  # Install dependencies from requirements.txt
158
156
  self.output.info("Installing dependencies from 'requirements.txt'...")
@@ -171,21 +169,11 @@ class Setup(ISetup):
171
169
  # Remove .git origin
172
170
  subprocess.run(["git", "remote", "remove", "origin"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
173
171
 
174
- # Invalidate Cache File
175
- try:
176
- temp_dir = tempfile.gettempdir()
177
- for filename in os.listdir(temp_dir):
178
- if filename.endswith('started.lab'):
179
- file_path = os.path.join(temp_dir, filename)
180
- os.remove(file_path)
181
- except Exception as e:
182
- self.output.fail("Unable to delete temporary files created by the framework on the device.")
183
-
184
172
  # Finish Process Message
185
173
  self.output.info(f"Project '{self.name_app_folder}' successfully created at '{os.path.abspath(project_path)}'.")
186
174
  self.output.endInstallation()
187
175
 
188
176
  except subprocess.CalledProcessError as e:
189
- self.output.error(f"Error while executing command: {e}")
177
+ raise ValueError(f"Error while executing command: {e}")
190
178
  except Exception as e:
191
- self.output.error(f"An unexpected error occurred: {e}")
179
+ raise RuntimeError(f"An unexpected error occurred: {e}")
orionis/luminate/app.py CHANGED
@@ -1,8 +1,6 @@
1
1
  import traceback
2
2
  from orionis.luminate.container.container import Container
3
- from orionis.luminate.cache.app.config import CacheConfig
4
- from orionis.luminate.bootstrap.config.register import Register
5
- from orionis.luminate.bootstrap.config.bootstrapper import Bootstrapper
3
+ from orionis.luminate.bootstrap.config_bootstrapper import Bootstrapper as BootstrapperConfig
6
4
  from orionis.luminate.patterns.singleton import SingletonMeta
7
5
 
8
6
  class Application(metaclass=SingletonMeta):
@@ -37,6 +35,16 @@ class Application(metaclass=SingletonMeta):
37
35
  self.booted = False
38
36
  self.error_info = None
39
37
 
38
+ self._config = {}
39
+
40
+ def register(self):
41
+ """
42
+ Registers all necessary service bindings with the application container.
43
+
44
+ This method is intended to be overridden by the user to define custom service bindings.
45
+ """
46
+ pass
47
+
40
48
  def boot(self):
41
49
  """
42
50
  Boots the application and registers necessary dependencies.
@@ -58,14 +66,10 @@ class Application(metaclass=SingletonMeta):
58
66
  return self
59
67
 
60
68
  try:
61
- # Register core services as singletons
62
- self.container.singleton(CacheConfig)
63
- self.container.singleton(Register)
64
- self.container.singleton(Bootstrapper)
65
-
66
- # Resolve the registered dependencies
67
- self.container.make(Register)
68
- self.container.make(Bootstrapper)
69
+ # Config Bootstrapper and retrieve application configuration
70
+ boot_config = self.container.singleton(BootstrapperConfig)
71
+ app_config = self.container.make(boot_config)
72
+ self._config = app_config.get()
69
73
 
70
74
  self.booted = True
71
75
  return self
@@ -0,0 +1,150 @@
1
+ import pathlib
2
+ import importlib
3
+ import inspect
4
+ from typing import Any, Callable, Dict, List
5
+ from orionis.contracts.bootstrap.i_command_bootstrapper import ICommandsBootstrapper
6
+ from orionis.luminate.bootstrap.exception_bootstrapper import BootstrapRuntimeError
7
+ from orionis.luminate.console.base.command import BaseCommand
8
+
9
+ class CommandsBootstrapper(ICommandsBootstrapper):
10
+ """
11
+ A class responsible for loading and registering console commands dynamically.
12
+
13
+ This class scans specified directories for Python files, imports them, and registers
14
+ command classes that inherit from `BaseCommand`. It ensures that commands are loaded
15
+ only once and provides methods to access and manage them.
16
+
17
+ Attributes
18
+ ----------
19
+ _commands : Dict[str, Dict[str, Any]]
20
+ A dictionary to store registered commands, where the key is the command signature
21
+ and the value is a dictionary containing the command class, arguments, description,
22
+ and signature.
23
+
24
+ Methods
25
+ -------
26
+ __init__()
27
+ Initializes the `CommandsBootstrapper` and triggers the autoload process.
28
+ _autoload()
29
+ Scans the command directories and loads command classes.
30
+ _register(concrete: Callable[..., Any])
31
+ Validates and registers a command class.
32
+ """
33
+
34
+ def __init__(self) -> None:
35
+ """
36
+ Initializes the `CommandsBootstrapper` and triggers the autoload process.
37
+
38
+ The `_commands` dictionary is initialized to store command data, and the
39
+ `_autoload` method is called to load commands from the specified directories.
40
+ """
41
+ self._commands: Dict[str, Dict[str, Any]] = {}
42
+ self._autoload()
43
+
44
+ def _autoload(self) -> None:
45
+ """
46
+ Scans the command directories and loads command classes.
47
+
48
+ This method searches for Python files in the specified directories, imports them,
49
+ and registers any class that inherits from `BaseCommand`.
50
+
51
+ Raises
52
+ ------
53
+ BootstrapRuntimeError
54
+ If there is an error loading a module.
55
+ """
56
+ base_path = pathlib.Path.cwd()
57
+
58
+ # Define the directories to scan for commands
59
+ command_dirs = [
60
+ base_path / "app" / "console" / "commands", # Developer-defined commands
61
+ pathlib.Path(__file__).resolve().parent.parent.parent / "console" / "commands" # Core commands
62
+ ]
63
+
64
+ for cmd_dir in command_dirs:
65
+ if not cmd_dir.is_dir():
66
+ continue
67
+
68
+ for file_path in cmd_dir.rglob("*.py"):
69
+ if file_path.name == "__init__.py":
70
+ continue
71
+
72
+ module_path = ".".join(file_path.relative_to(base_path).with_suffix("").parts)
73
+
74
+ # Remove 'site-packages.' prefix if present
75
+ if 'site-packages.' in module_path:
76
+ module_path = module_path.split('site-packages.')[1]
77
+
78
+ try:
79
+ module = importlib.import_module(module_path.strip())
80
+
81
+ # Find and register command classes
82
+ for name, concrete in inspect.getmembers(module, inspect.isclass):
83
+ if issubclass(concrete, BaseCommand) and concrete is not BaseCommand:
84
+ self._register(concrete)
85
+ except Exception as e:
86
+ raise BootstrapRuntimeError(f"Error loading {module_path}") from e
87
+
88
+ def _register(self, concrete: Callable[..., Any]) -> None:
89
+ """
90
+ Validates and registers a command class.
91
+
92
+ This method ensures that the provided class is valid (inherits from `BaseCommand`,
93
+ has a `signature`, `description`, and `handle` method) and registers it in the
94
+ `_commands` dictionary.
95
+
96
+ Parameters
97
+ ----------
98
+ concrete : Callable[..., Any]
99
+ The command class to register.
100
+
101
+ Raises
102
+ ------
103
+ TypeError
104
+ If the input is not a class or does not inherit from `BaseCommand`.
105
+ ValueError
106
+ If the class does not have required attributes or methods.
107
+ """
108
+ if not isinstance(concrete, type):
109
+ raise TypeError(f"Expected a class, but got {type(concrete).__name__}.")
110
+
111
+ # Validate 'signature' attribute
112
+ if not hasattr(concrete, 'signature') or not isinstance(concrete.signature, str):
113
+ raise ValueError(f"Class {concrete.__name__} must have a 'signature' attribute as a string.")
114
+
115
+ signature = concrete.signature.strip()
116
+
117
+ # Validate signature format
118
+ if not signature or ' ' in signature or not all(c.isalnum() or c == ":" for c in signature):
119
+ raise ValueError(f"Invalid signature format: '{signature}'. Only letters, numbers, and ':' are allowed, with no spaces.")
120
+
121
+ # Validate 'description' attribute
122
+ if not hasattr(concrete, 'description') or not isinstance(concrete.description, str):
123
+ raise ValueError(f"Class {concrete.__name__} must have a 'description' attribute as a string.")
124
+
125
+ description = concrete.description.strip()
126
+
127
+ # Validate 'handle' method
128
+ if not hasattr(concrete, 'handle') or not callable(getattr(concrete, 'handle')):
129
+ raise ValueError(f"Class {concrete.__name__} must implement a 'handle' method.")
130
+
131
+ # Validate 'arguments' method (optional)
132
+ arguments: List[Any] = []
133
+ if hasattr(concrete, 'arguments') and callable(getattr(concrete, 'arguments')):
134
+ arguments = concrete().arguments()
135
+
136
+ # Validate inheritance from 'BaseCommand'
137
+ if not issubclass(concrete, BaseCommand):
138
+ raise TypeError(f"Class {concrete.__name__} must inherit from 'BaseCommand'.")
139
+
140
+ # Ensure the command signature is unique
141
+ if signature in self._commands:
142
+ raise ValueError(f"Command '{signature}' is already registered. Please ensure signatures are unique.")
143
+
144
+ # Register the command
145
+ self._commands[signature] = {
146
+ 'concrete': concrete,
147
+ 'arguments': arguments,
148
+ 'description': description,
149
+ 'signature': signature
150
+ }
@@ -0,0 +1,168 @@
1
+ import importlib
2
+ import pathlib
3
+ from dataclasses import asdict
4
+ from typing import Any, Dict
5
+ from orionis.contracts.bootstrap.i_config_bootstrapper import IConfigBootstrapper
6
+ from orionis.contracts.config.i_config import IConfig
7
+ from orionis.luminate.bootstrap.exception_bootstrapper import BootstrapRuntimeError
8
+
9
+ class ConfigBootstrapper(IConfigBootstrapper):
10
+ """
11
+ A class responsible for loading and registering application configurations dynamically.
12
+
13
+ This class scans a specified directory for Python files, imports them, and registers
14
+ configuration classes that inherit from `IConfig`. It ensures that configurations
15
+ are loaded only once and provides methods to access and modify them.
16
+
17
+ Attributes
18
+ ----------
19
+ _config : Dict[str, Any]
20
+ A dictionary to store registered configuration sections and their data.
21
+
22
+ Methods
23
+ -------
24
+ __init__()
25
+ Initializes the `ConfigBootstrapper` and triggers the autoload process.
26
+ _autoload()
27
+ Scans the configuration directory and loads configuration classes.
28
+ _set(concrete: Any, section: str)
29
+ Validates and registers a configuration class.
30
+ _parse(data: Any) -> Dict[str, Any]
31
+ Converts the 'config' attribute of a class into a dictionary.
32
+ _register(section: str, data: Dict[str, Any])
33
+ Registers a configuration section.
34
+ set(key: str, value: Any)
35
+ Dynamically sets a configuration value using dot notation.
36
+ get(key: str, default: Optional[Any] = None) -> Any
37
+ Retrieves a configuration value using dot notation.
38
+ """
39
+
40
+ def __init__(self) -> None:
41
+ """
42
+ Initializes the `ConfigBootstrapper` and triggers the autoload process.
43
+
44
+ The `_config` dictionary is initialized to store configuration data, and the
45
+ `_autoload` method is called to load configurations from the default directory.
46
+ """
47
+ self._config: Dict[str, Any] = {}
48
+ self._autoload()
49
+
50
+ def _autoload(self) -> None:
51
+ """
52
+ Scans the configuration directory and loads configuration classes.
53
+
54
+ This method searches for Python files in the specified directory, imports them,
55
+ and registers any class named `Config` that inherits from `IConfig`.
56
+
57
+ Raises
58
+ ------
59
+ FileNotFoundError
60
+ If the configuration directory does not exist.
61
+ BootstrapRuntimeError
62
+ If there is an error loading a module.
63
+ """
64
+ directory = "config"
65
+ base_path = pathlib.Path(directory).resolve()
66
+
67
+ if not base_path.exists():
68
+ raise FileNotFoundError(f"Directory {directory} does not exist.")
69
+
70
+ for file_path in base_path.rglob("*.py"):
71
+ if file_path.name == "__init__.py":
72
+ continue
73
+
74
+ module_path = ".".join(file_path.relative_to(base_path).with_suffix("").parts)
75
+
76
+ try:
77
+ module = importlib.import_module(f"{directory}.{module_path}")
78
+ if hasattr(module, "Config"):
79
+ self._set(
80
+ concrete=getattr(module, "Config"),
81
+ section=module_path
82
+ )
83
+ except Exception as e:
84
+ raise BootstrapRuntimeError(f"Error loading module {module_path}") from e
85
+
86
+ def _set(self, concrete: Any, section: str) -> None:
87
+ """
88
+ Validates and registers a configuration class.
89
+
90
+ This method ensures that the provided class is valid (inherits from `IConfig`
91
+ and has a `config` attribute) and registers it in the `_config` dictionary.
92
+
93
+ Parameters
94
+ ----------
95
+ concrete : Any
96
+ The configuration class to register.
97
+ section : str
98
+ The section name under which the configuration will be registered.
99
+
100
+ Raises
101
+ ------
102
+ TypeError
103
+ If the input is not a class or does not inherit from `IConfig`.
104
+ ValueError
105
+ If the class does not have a `config` attribute.
106
+ """
107
+ if not isinstance(concrete, type):
108
+ raise TypeError(f"Expected a class, but got {type(concrete).__name__}.")
109
+
110
+ if not hasattr(concrete, "config"):
111
+ raise ValueError(f"Class {concrete.__name__} must have a 'config' attribute.")
112
+
113
+ if not issubclass(concrete, IConfig):
114
+ raise TypeError(f"Class {concrete.__name__} must inherit from 'IConfig'.")
115
+
116
+ self._register(
117
+ section=section,
118
+ data=self._parse(concrete.config)
119
+ )
120
+
121
+ def _parse(self, data: Any) -> Dict[str, Any]:
122
+ """
123
+ Converts the 'config' attribute of a class into a dictionary.
124
+
125
+ If the input is already a dictionary, it is returned as-is. If the input is a
126
+ dataclass, it is converted to a dictionary using `asdict`.
127
+
128
+ Parameters
129
+ ----------
130
+ data : Any
131
+ The data to convert into a dictionary.
132
+
133
+ Returns
134
+ -------
135
+ Dict[str, Any]
136
+ The converted dictionary.
137
+
138
+ Raises
139
+ ------
140
+ TypeError
141
+ If the data cannot be converted to a dictionary.
142
+ """
143
+ if isinstance(data, dict):
144
+ return data
145
+ try:
146
+ return asdict(data)
147
+ except TypeError as e:
148
+ raise TypeError(f"Error: The 'config' attribute could not be converted to a dictionary. {str(e)}")
149
+
150
+ def _register(self, section: str, data: Dict[str, Any]) -> None:
151
+ """
152
+ Registers a configuration section.
153
+
154
+ Parameters
155
+ ----------
156
+ section : str
157
+ The name of the configuration section.
158
+ data : Dict[str, Any]
159
+ The configuration data to register.
160
+
161
+ Raises
162
+ ------
163
+ ValueError
164
+ If the section is already registered.
165
+ """
166
+ if section in self._config:
167
+ raise ValueError(f"Configuration section '{section}' is already registered.")
168
+ self._config[section] = data
@@ -0,0 +1,67 @@
1
+ import os
2
+ from pathlib import Path
3
+ from typing import Dict
4
+ from dotenv import dotenv_values
5
+ from orionis.contracts.bootstrap.i_environment_bootstrapper import IEnvironmentBootstrapper
6
+ from orionis.luminate.bootstrap.exception_bootstrapper import BootstrapRuntimeError
7
+
8
+ class EnvironmentBootstrapper(IEnvironmentBootstrapper):
9
+ """
10
+ A class responsible for loading and managing environment variables from a `.env` file.
11
+
12
+ This class implements the `IEnvironment` interface and provides functionality to
13
+ automatically load environment variables from a `.env` file located in the current
14
+ working directory. If the file does not exist, it creates it.
15
+
16
+ Attributes
17
+ ----------
18
+ _environment_vars : Dict[str, str]
19
+ A dictionary to store the loaded environment variables.
20
+ path : Path
21
+ The path to the `.env` file.
22
+
23
+ Methods
24
+ -------
25
+ __init__()
26
+ Initializes the `EnvironmentBootstrapper` and triggers the autoload process.
27
+ _autoload()
28
+ Loads environment variables from the `.env` file or creates the file if it does not exist.
29
+ """
30
+
31
+ def __init__(self) -> None:
32
+ """
33
+ Initializes the `EnvironmentBootstrapper` and triggers the autoload process.
34
+
35
+ The `_environment_vars` dictionary is initialized to store environment variables,
36
+ and the `_autoload` method is called to load variables from the `.env` file.
37
+ """
38
+ self._environment_vars: Dict[str, str] = {}
39
+ self._autoload()
40
+
41
+ def _autoload(self) -> None:
42
+ """
43
+ Loads environment variables from the `.env` file or creates the file if it does not exist.
44
+
45
+ This method checks if the `.env` file exists in the current working directory.
46
+ If the file does not exist, it creates an empty `.env` file. If the file exists,
47
+ it loads the environment variables into the `_environment_vars` dictionary.
48
+
49
+ Raises
50
+ ------
51
+ PermissionError
52
+ If the `.env` file cannot be created or read due to insufficient permissions.
53
+ """
54
+ # Set the path to the `.env` file
55
+ path: Path = Path(os.getcwd()) / ".env"
56
+
57
+ # Create the `.env` file if it does not exist
58
+ if not path.exists():
59
+ try:
60
+ path.touch() # Create an empty `.env` file if it does not exist
61
+ except PermissionError as e:
62
+ raise PermissionError(f"Cannot create `.env` file at {path}: {str(e)}")
63
+
64
+ try:
65
+ self._environment_vars = dotenv_values(path) # Load environment variables
66
+ except Exception as e:
67
+ raise BootstrapRuntimeError(f"Error loading environment variables from {path}: {str(e)}")
@@ -0,0 +1,20 @@
1
+ from orionis.luminate.support.environment import Environment
2
+
3
+ def env(key: str, default=None) -> str:
4
+ """
5
+ Retrieves the value of an environment variable from the .env file
6
+ or from system environment variables if not found.
7
+
8
+ Parameters
9
+ ----------
10
+ key : str
11
+ The key of the environment variable.
12
+ default : optional
13
+ Default value if the key does not exist. Defaults to None.
14
+
15
+ Returns
16
+ -------
17
+ str
18
+ The value of the environment variable or the default value.
19
+ """
20
+ return Environment().get(key, default)
@@ -1,6 +1,6 @@
1
+ from orionis.contracts.console.base.i_command import IBaseCommand
1
2
  from orionis.luminate.console.output.console import Console
2
3
  from orionis.luminate.console.output.progress_bar import ProgressBar
3
- from orionis.luminate.contracts.console.base.base_command_interface import IBaseCommand
4
4
 
5
5
  class BaseCommand(IBaseCommand):
6
6
  """
@@ -404,10 +404,8 @@ class BaseCommand(IBaseCommand):
404
404
  Contain the arguments to be set for the command.
405
405
  """
406
406
  try:
407
- # Convert arguments to a dictionary using vars(), if possible
408
407
  self.args = vars(args)
409
408
  except TypeError:
410
- # Fallback to an empty dictionary if args is not compatible with vars()
411
409
  self.args = {}
412
410
 
413
411
 
@@ -1,6 +1,6 @@
1
- from orionis.luminate.contracts.console.command_filter_interface import ICommandFilter
1
+ from orionis.contracts.console.i_command_filter import ICommandFilter
2
2
 
3
- # List of commands that should be excluded from output formatting
3
+ # List of commands to exclude from output formatting
4
4
  EXCLUDED_COMMANDS = [
5
5
  'schedule:work', # Command to handle scheduled work
6
6
  'help', # Command to show help information