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.
- orionis/cli_manager.py +16 -30
- orionis/contracts/bootstrap/i_command_bootstrapper.py +59 -0
- orionis/contracts/bootstrap/i_config_bootstrapper.py +112 -0
- orionis/contracts/bootstrap/i_environment_bootstrapper.py +33 -0
- orionis/{luminate/contracts/console/base/base_command_interface.py → contracts/console/base/i_command.py} +0 -1
- orionis/contracts/facades/config/i_config_facade.py +37 -0
- orionis/contracts/facades/environment/i_environment_facade.py +40 -0
- orionis/{luminate/contracts/facades/paths_interface.py → contracts/facades/files/i_path_facade.py} +27 -20
- orionis/contracts/facades/log/i_log_facade.py +83 -0
- orionis/{luminate/contracts/facades/tests_interface.py → contracts/facades/tests/i_tests_facade.py} +3 -6
- orionis/{luminate/contracts/console/scripts/management_interface.py → contracts/installer/i_installer_manager.py} +19 -16
- orionis/contracts/installer/i_installer_output.py +101 -0
- orionis/contracts/installer/i_installer_setup.py +59 -0
- orionis/contracts/providers/i_service_provider.py +27 -0
- orionis/contracts/services/config/i_config_service.py +37 -0
- orionis/contracts/services/files/i_path_service.py +31 -0
- orionis/contracts/services/log/i_log_service.py +89 -0
- orionis/{luminate/facades/environment.py → contracts/support/i_environment.py} +25 -32
- orionis/{luminate/contracts/tools/exception_to_dict_interface.py → contracts/support/i_exception_to_dict.py} +0 -9
- orionis/{luminate/contracts/tools/reflection_interface.py → contracts/support/i_reflection.py} +17 -8
- orionis/{luminate/contracts/tools/std_interface.py → contracts/support/i_std.py} +3 -16
- orionis/framework.py +1 -1
- orionis/{luminate/console/scripts/management.py → installer/installer_manager.py} +22 -16
- orionis/{luminate/installer/output.py → installer/installer_output.py} +16 -10
- orionis/{luminate/installer/setup.py → installer/installer_setup.py} +13 -25
- orionis/luminate/app.py +15 -11
- orionis/luminate/bootstrap/command_bootstrapper.py +150 -0
- orionis/luminate/bootstrap/config_bootstrapper.py +168 -0
- orionis/luminate/bootstrap/environment_bootstrapper.py +67 -0
- orionis/luminate/config/helpers.py +20 -0
- orionis/luminate/console/base/command.py +1 -3
- orionis/luminate/console/command_filter.py +2 -2
- orionis/luminate/console/commands/cache_clear.py +17 -5
- orionis/luminate/console/commands/help.py +19 -13
- orionis/luminate/console/commands/schedule_work.py +6 -3
- orionis/luminate/console/commands/tests.py +1 -6
- orionis/luminate/console/commands/version.py +0 -5
- orionis/luminate/console/exceptions/cli_exception.py +1 -4
- orionis/luminate/console/kernel.py +2 -2
- orionis/luminate/console/output/console.py +2 -2
- orionis/luminate/console/output/executor.py +1 -1
- orionis/luminate/console/output/progress_bar.py +1 -1
- orionis/luminate/console/parser.py +2 -2
- orionis/luminate/console/runner.py +127 -126
- orionis/luminate/console/tasks/scheduler.py +8 -7
- orionis/luminate/container/container.py +1 -1
- orionis/luminate/container/types.py +38 -12
- orionis/luminate/facades/config/config_facade.py +43 -0
- orionis/luminate/facades/environment/environment_facade.py +68 -0
- orionis/luminate/facades/{paths.py → files/path_facade.py} +50 -6
- orionis/luminate/facades/log/__init__.py +0 -0
- orionis/luminate/facades/log/log_facade.py +95 -0
- orionis/luminate/facades/tests/__init__.py +0 -0
- orionis/luminate/facades/{tests.py → tests/tests_facade.py} +1 -1
- orionis/luminate/patterns/__init__.py +0 -0
- orionis/luminate/pipelines/cli_pipeline.py +122 -116
- orionis/luminate/providers/__init__.py +0 -0
- orionis/luminate/providers/config/__init__.py +0 -0
- orionis/luminate/providers/config/config_service_provider.py +26 -0
- orionis/luminate/providers/files/__init__.py +0 -0
- orionis/luminate/providers/files/path_service_provider.py +26 -0
- orionis/luminate/providers/log/__init__.py +0 -0
- orionis/luminate/providers/log/log_service_provider.py +26 -0
- orionis/luminate/services/__init__.py +0 -0
- orionis/luminate/services/config/__init__.py +0 -0
- orionis/luminate/services/config/config_service.py +72 -0
- orionis/luminate/services/files/__init__.py +0 -0
- orionis/luminate/services/files/path_service.py +71 -0
- orionis/luminate/services/log/__init__.py +0 -0
- orionis/luminate/services/log/log_service.py +159 -0
- orionis/luminate/{config → support}/environment.py +21 -32
- orionis/luminate/{tools → support}/exception_to_dict.py +1 -1
- orionis/luminate/{tools → support}/reflection.py +1 -1
- orionis/luminate/{tools → support}/std.py +1 -1
- orionis/luminate/test/unit_test.py +1 -2
- {orionis-0.44.0.dist-info → orionis-0.47.0.dist-info}/METADATA +1 -1
- orionis-0.47.0.dist-info/RECORD +157 -0
- tests/tools/test_reflection.py +1 -1
- orionis/luminate/bootstrap/commands/bootstrapper.py +0 -101
- orionis/luminate/bootstrap/commands/register.py +0 -92
- orionis/luminate/bootstrap/config/bootstrapper.py +0 -79
- orionis/luminate/bootstrap/config/parser.py +0 -53
- orionis/luminate/bootstrap/config/register.py +0 -76
- orionis/luminate/cache/app/config.py +0 -91
- orionis/luminate/cache/console/commands.py +0 -97
- orionis/luminate/console/cache.py +0 -87
- orionis/luminate/console/command.py +0 -40
- orionis/luminate/contracts/bootstrap/commands/bootstrapper_interface.py +0 -44
- orionis/luminate/contracts/bootstrap/commands/register_interface.py +0 -33
- orionis/luminate/contracts/bootstrap/config/bootstrapper_interface.py +0 -40
- orionis/luminate/contracts/bootstrap/config/parser_interface.py +0 -46
- orionis/luminate/contracts/bootstrap/config/register_interface.py +0 -47
- orionis/luminate/contracts/cache/app/config_interface.py +0 -76
- orionis/luminate/contracts/cache/console/commands_interface.py +0 -78
- orionis/luminate/contracts/config/environment_interface.py +0 -64
- orionis/luminate/contracts/console/command_interface.py +0 -36
- orionis/luminate/contracts/console/runner_interface.py +0 -50
- orionis/luminate/contracts/facades/env_interface.py +0 -64
- orionis/luminate/contracts/facades/log_interface.py +0 -48
- orionis/luminate/contracts/files/paths_interface.py +0 -29
- orionis/luminate/contracts/installer/output_interface.py +0 -125
- orionis/luminate/contracts/installer/setup_interface.py +0 -29
- orionis/luminate/contracts/installer/upgrade_interface.py +0 -24
- orionis/luminate/contracts/log/logger_interface.py +0 -33
- orionis/luminate/contracts/pipelines/cli_pipeline_interface.py +0 -84
- orionis/luminate/contracts/publisher/pypi_publisher_interface.py +0 -36
- orionis/luminate/contracts/test/unit_test_interface.py +0 -51
- orionis/luminate/facades/config.py +0 -10
- orionis/luminate/facades/log.py +0 -56
- orionis/luminate/files/paths.py +0 -56
- orionis/luminate/installer/upgrade.py +0 -42
- orionis/luminate/log/logger.py +0 -116
- orionis/luminate/publisher/pypi.py +0 -215
- orionis-0.44.0.dist-info/RECORD +0 -156
- /orionis/{luminate/bootstrap/config → contracts}/__init__.py +0 -0
- /orionis/{luminate/cache → contracts/config}/__init__.py +0 -0
- /orionis/{luminate/contracts/config/config_interface.py → contracts/config/i_config.py} +0 -0
- /orionis/{luminate/cache/app → contracts/console}/__init__.py +0 -0
- /orionis/{luminate/cache/console → contracts/console/base}/__init__.py +0 -0
- /orionis/{luminate/contracts/console/command_filter_interface.py → contracts/console/i_command_filter.py} +0 -0
- /orionis/{luminate/contracts/console/kernel_interface.py → contracts/console/i_kernel.py} +0 -0
- /orionis/{luminate/contracts/console/parser_interface.py → contracts/console/i_parser.py} +0 -0
- /orionis/{luminate/contracts/console/task_manager_interface.py → contracts/console/i_task_manager.py} +0 -0
- /orionis/{luminate/config/dataclass → contracts/console/output}/__init__.py +0 -0
- /orionis/{luminate/contracts/console/output/console_interface.py → contracts/console/output/i_console.py} +0 -0
- /orionis/{luminate/contracts/console/output/executor_interface.py → contracts/console/output/i_executor.py} +0 -0
- /orionis/{luminate/contracts/console/output/progress_bar_interface.py → contracts/console/output/i_progress_bar.py} +0 -0
- /orionis/{luminate/console/scripts → contracts/console/tasks}/__init__.py +0 -0
- /orionis/{luminate/contracts/console/tasks/schedule_interface.py → contracts/console/tasks/i_schedule.py} +0 -0
- /orionis/{luminate/contracts/container/container_interface.py → contracts/container/i_container.py} +0 -0
- /orionis/{luminate/contracts/container/types_interface.py → contracts/container/i_types.py} +0 -0
- /orionis/{luminate/contracts → contracts/facades}/__init__.py +0 -0
- /orionis/{luminate/contracts/cache → contracts/facades/config}/__init__.py +0 -0
- /orionis/{luminate/contracts/config → contracts/facades/environment}/__init__.py +0 -0
- /orionis/{luminate/contracts/console → contracts/facades/files}/__init__.py +0 -0
- /orionis/{luminate/contracts/facades → contracts/facades/log}/__init__.py +0 -0
- /orionis/{luminate/contracts/files → contracts/facades/tests}/__init__.py +0 -0
- /orionis/{luminate/contracts → contracts}/installer/__init__.py +0 -0
- /orionis/{luminate/contracts/log → contracts/providers}/__init__.py +0 -0
- /orionis/{luminate/contracts/publisher → contracts/services}/__init__.py +0 -0
- /orionis/{luminate/contracts/test → contracts/services/config}/__init__.py +0 -0
- /orionis/{luminate/contracts/tools → contracts/services/files}/__init__.py +0 -0
- /orionis/{luminate/files → contracts/services/log}/__init__.py +0 -0
- /orionis/{luminate/installer → installer}/__init__.py +0 -0
- /orionis/luminate/bootstrap/{cli_exception.py → exception_bootstrapper.py} +0 -0
- /orionis/luminate/config/{dataclass/app.py → app.py} +0 -0
- /orionis/luminate/config/{dataclass/auth.py → auth.py} +0 -0
- /orionis/luminate/config/{dataclass/cache.py → cache.py} +0 -0
- /orionis/luminate/config/{dataclass/cors.py → cors.py} +0 -0
- /orionis/luminate/config/{dataclass/database.py → database.py} +0 -0
- /orionis/luminate/config/{dataclass/filesystems.py → filesystems.py} +0 -0
- /orionis/luminate/config/{dataclass/logging.py → logging.py} +0 -0
- /orionis/luminate/config/{dataclass/mail.py → mail.py} +0 -0
- /orionis/luminate/config/{dataclass/queue.py → queue.py} +0 -0
- /orionis/luminate/config/{dataclass/session.py → session.py} +0 -0
- /orionis/luminate/{log → facades/config}/__init__.py +0 -0
- /orionis/luminate/{publisher → facades/environment}/__init__.py +0 -0
- /orionis/luminate/{tools → facades/files}/__init__.py +0 -0
- /orionis/luminate/{tools → support}/dot_dict.py +0 -0
- /orionis/{luminate/installer → static/ascii}/icon.ascii +0 -0
- /orionis/{luminate/installer → static/ascii}/info.ascii +0 -0
- /orionis/{luminate/static → static}/bg/galaxy.jpg +0 -0
- /orionis/{luminate/static → static}/favicon/OrionisFrameworkFavicon.png +0 -0
- /orionis/{luminate/static → static}/logos/OrionisFramework.jpg +0 -0
- /orionis/{luminate/static → static}/logos/OrionisFramework.png +0 -0
- /orionis/{luminate/static → static}/logos/OrionisFramework.psd +0 -0
- /orionis/{luminate/static → static}/logos/OrionisFramework2.png +0 -0
- /orionis/{luminate/static → static}/logos/OrionisFramework3.png +0 -0
- {orionis-0.44.0.dist-info → orionis-0.47.0.dist-info}/LICENCE +0 -0
- {orionis-0.44.0.dist-info → orionis-0.47.0.dist-info}/WHEEL +0 -0
- {orionis-0.44.0.dist-info → orionis-0.47.0.dist-info}/entry_points.txt +0 -0
- {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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
187
|
-
|
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.
|
10
|
-
from orionis.luminate.contracts.installer.setup_interface import ISetup
|
8
|
+
from orionis.installer.installer_output import InstallerOutput
|
11
9
|
|
12
|
-
class
|
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
|
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 =
|
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 :
|
45
|
-
An instance of
|
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
|
-
|
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
|
-
|
102
|
+
raise ValueError("The folder name can only contain letters, numbers, underscores, and hyphens.")
|
105
103
|
|
106
104
|
if not name:
|
107
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
177
|
+
raise ValueError(f"Error while executing command: {e}")
|
190
178
|
except Exception as e:
|
191
|
-
|
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.
|
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
|
-
#
|
62
|
-
self.container.singleton(
|
63
|
-
self.container.
|
64
|
-
self.
|
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.
|
1
|
+
from orionis.contracts.console.i_command_filter import ICommandFilter
|
2
2
|
|
3
|
-
# List of commands
|
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
|