orionis 0.1.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.
- orionis/__init__.py +0 -0
- orionis/cli_manager.py +48 -0
- orionis/framework.py +45 -0
- orionis/luminate/__init__.py +0 -0
- orionis/luminate/app.py +0 -0
- orionis/luminate/bootstrap/__init__.py +0 -0
- orionis/luminate/bootstrap/parser.py +49 -0
- orionis/luminate/bootstrap/register.py +95 -0
- orionis/luminate/cache/__init__.py +0 -0
- orionis/luminate/cache/app/__init__.py +0 -0
- orionis/luminate/cache/app/config.py +96 -0
- orionis/luminate/cache/console/__init__.py +0 -0
- orionis/luminate/cache/console/commands.py +98 -0
- orionis/luminate/config/__init__.py +0 -0
- orionis/luminate/config/dataclass/__init__.py +0 -0
- orionis/luminate/config/dataclass/app.py +50 -0
- orionis/luminate/config/dataclass/auth.py +17 -0
- orionis/luminate/config/dataclass/cache.py +51 -0
- orionis/luminate/config/dataclass/cors.py +58 -0
- orionis/luminate/config/dataclass/database.py +203 -0
- orionis/luminate/config/dataclass/filesystems.py +102 -0
- orionis/luminate/config/dataclass/logging.py +107 -0
- orionis/luminate/config/dataclass/mail.py +81 -0
- orionis/luminate/config/dataclass/queue.py +63 -0
- orionis/luminate/config/dataclass/session.py +59 -0
- orionis/luminate/config/environment.py +110 -0
- orionis/luminate/config/sections.py +37 -0
- orionis/luminate/console/__init__.py +0 -0
- orionis/luminate/console/base/__init__.py +0 -0
- orionis/luminate/console/base/command.py +427 -0
- orionis/luminate/console/cache.py +132 -0
- orionis/luminate/console/command.py +40 -0
- orionis/luminate/console/command_filter.py +40 -0
- orionis/luminate/console/commands/__init__.py +0 -0
- orionis/luminate/console/commands/cache_clear.py +56 -0
- orionis/luminate/console/commands/help.py +59 -0
- orionis/luminate/console/commands/schedule_work.py +50 -0
- orionis/luminate/console/commands/tests.py +40 -0
- orionis/luminate/console/commands/version.py +39 -0
- orionis/luminate/console/exceptions/__init__.py +0 -0
- orionis/luminate/console/exceptions/cli_exception.py +125 -0
- orionis/luminate/console/kernel.py +32 -0
- orionis/luminate/console/output/__init__.py +0 -0
- orionis/luminate/console/output/console.py +426 -0
- orionis/luminate/console/output/executor.py +90 -0
- orionis/luminate/console/output/progress_bar.py +100 -0
- orionis/luminate/console/output/styles.py +95 -0
- orionis/luminate/console/parser.py +159 -0
- orionis/luminate/console/register.py +98 -0
- orionis/luminate/console/runner.py +126 -0
- orionis/luminate/console/scripts/__init__.py +0 -0
- orionis/luminate/console/scripts/management.py +94 -0
- orionis/luminate/console/tasks/__init__.py +0 -0
- orionis/luminate/console/tasks/scheduler.py +616 -0
- orionis/luminate/contracts/__init__.py +0 -0
- orionis/luminate/contracts/bootstrap/parser_interface.py +46 -0
- orionis/luminate/contracts/cache/__init__.py +0 -0
- orionis/luminate/contracts/cache/cache_commands_interface.py +69 -0
- orionis/luminate/contracts/config/__init__.py +0 -0
- orionis/luminate/contracts/config/config_interface.py +27 -0
- orionis/luminate/contracts/config/environment_interface.py +64 -0
- orionis/luminate/contracts/console/__init__.py +0 -0
- orionis/luminate/contracts/console/base_command_interface.py +448 -0
- orionis/luminate/contracts/console/cli_cache_interface.py +34 -0
- orionis/luminate/contracts/console/command_filter_interface.py +32 -0
- orionis/luminate/contracts/console/command_interface.py +36 -0
- orionis/luminate/contracts/console/console_interface.py +305 -0
- orionis/luminate/contracts/console/executor_interface.py +51 -0
- orionis/luminate/contracts/console/kernel_interface.py +32 -0
- orionis/luminate/contracts/console/management_interface.py +63 -0
- orionis/luminate/contracts/console/parser_interface.py +76 -0
- orionis/luminate/contracts/console/progress_bar_interface.py +66 -0
- orionis/luminate/contracts/console/register_interface.py +32 -0
- orionis/luminate/contracts/console/runner_interface.py +50 -0
- orionis/luminate/contracts/console/schedule_interface.py +317 -0
- orionis/luminate/contracts/console/task_manager_interface.py +37 -0
- orionis/luminate/contracts/facades/__init__.py +0 -0
- orionis/luminate/contracts/facades/env_interface.py +64 -0
- orionis/luminate/contracts/facades/log_interface.py +48 -0
- orionis/luminate/contracts/facades/paths_interface.py +141 -0
- orionis/luminate/contracts/facades/tests_interface.py +33 -0
- orionis/luminate/contracts/files/__init__.py +0 -0
- orionis/luminate/contracts/files/paths_interface.py +29 -0
- orionis/luminate/contracts/installer/__init__.py +0 -0
- orionis/luminate/contracts/installer/output_interface.py +125 -0
- orionis/luminate/contracts/installer/setup_interface.py +29 -0
- orionis/luminate/contracts/installer/upgrade_interface.py +24 -0
- orionis/luminate/contracts/log/__init__.py +0 -0
- orionis/luminate/contracts/log/logger_interface.py +33 -0
- orionis/luminate/contracts/pipelines/cli_pipeline_interface.py +84 -0
- orionis/luminate/contracts/publisher/__init__.py +0 -0
- orionis/luminate/contracts/publisher/pypi_publisher_interface.py +36 -0
- orionis/luminate/contracts/test/__init__.py +0 -0
- orionis/luminate/contracts/test/unit_test_interface.py +51 -0
- orionis/luminate/contracts/tools/__init__.py +0 -0
- orionis/luminate/contracts/tools/reflection_interface.py +343 -0
- orionis/luminate/contracts/tools/std_interface.py +56 -0
- orionis/luminate/facades/__init__.py +0 -0
- orionis/luminate/facades/environment.py +81 -0
- orionis/luminate/facades/log.py +56 -0
- orionis/luminate/facades/paths.py +268 -0
- orionis/luminate/facades/tests.py +48 -0
- orionis/luminate/files/__init__.py +0 -0
- orionis/luminate/files/paths.py +56 -0
- orionis/luminate/installer/__init__.py +0 -0
- orionis/luminate/installer/icon.ascii +11 -0
- orionis/luminate/installer/info.ascii +13 -0
- orionis/luminate/installer/output.py +188 -0
- orionis/luminate/installer/setup.py +191 -0
- orionis/luminate/installer/upgrade.py +42 -0
- orionis/luminate/log/__init__.py +0 -0
- orionis/luminate/log/logger.py +116 -0
- orionis/luminate/pipelines/__init__.py +0 -0
- orionis/luminate/pipelines/cli_pipeline.py +116 -0
- orionis/luminate/publisher/__init__.py +0 -0
- orionis/luminate/publisher/pypi.py +206 -0
- orionis/luminate/static/logos/flaskavel.png +0 -0
- orionis/luminate/test/__init__.py +0 -0
- orionis/luminate/test/exception.py +48 -0
- orionis/luminate/test/unit_test.py +108 -0
- orionis/luminate/tools/__init__.py +0 -0
- orionis/luminate/tools/reflection.py +390 -0
- orionis/luminate/tools/std.py +53 -0
- orionis-0.1.0.dist-info/LICENCE +21 -0
- orionis-0.1.0.dist-info/METADATA +27 -0
- orionis-0.1.0.dist-info/RECORD +133 -0
- orionis-0.1.0.dist-info/WHEEL +5 -0
- orionis-0.1.0.dist-info/entry_points.txt +2 -0
- orionis-0.1.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +0 -0
- tests/tools/__init__.py +0 -0
- tests/tools/class_example.py +50 -0
- tests/tools/test_reflection.py +128 -0
@@ -0,0 +1,191 @@
|
|
1
|
+
import re
|
2
|
+
import os
|
3
|
+
import sys
|
4
|
+
import shutil
|
5
|
+
import tempfile
|
6
|
+
import subprocess
|
7
|
+
from unicodedata import normalize
|
8
|
+
from orionis.framework import SKELETON, DOCS
|
9
|
+
from orionis.luminate.installer.output import Output
|
10
|
+
from orionis.luminate.contracts.installer.setup_interface import ISetup
|
11
|
+
|
12
|
+
class Setup(ISetup):
|
13
|
+
"""
|
14
|
+
A class to initialize a Orionis project by performing the following setup actions:
|
15
|
+
1. Sanitize the folder name.
|
16
|
+
2. Clone the repository.
|
17
|
+
3. Create a virtual environment.
|
18
|
+
4. Install dependencies from requirements.txt.
|
19
|
+
5. Set up .env configuration.
|
20
|
+
6. Generate an API key.
|
21
|
+
7. Clean up temporary files and .git origin.
|
22
|
+
|
23
|
+
Parameters
|
24
|
+
----------
|
25
|
+
output : Output
|
26
|
+
An instance of Output, used to display messages to the console.
|
27
|
+
name_app : str, optional
|
28
|
+
The name of the app to create. If not provided, defaults to "{NAME}_app".
|
29
|
+
|
30
|
+
Attributes
|
31
|
+
----------
|
32
|
+
output : Output
|
33
|
+
An instance of Output used for logging information.
|
34
|
+
name_app_folder : str
|
35
|
+
The sanitized folder name for the application.
|
36
|
+
"""
|
37
|
+
|
38
|
+
def __init__(self, name: str = 'example-app', output = Output):
|
39
|
+
"""
|
40
|
+
Initialize OrionislInit class.
|
41
|
+
|
42
|
+
Parameters
|
43
|
+
----------
|
44
|
+
output : Output
|
45
|
+
An instance of Output to handle logging.
|
46
|
+
name_app : str, optional
|
47
|
+
Name of the application. If not provided, defaults to "example-app".
|
48
|
+
"""
|
49
|
+
self.output = output
|
50
|
+
self.output.startInstallation()
|
51
|
+
self.name_app_folder = self._sanitize_folder_name(name)
|
52
|
+
|
53
|
+
def _sanitize_folder_name(self, name: str) -> str:
|
54
|
+
"""
|
55
|
+
Sanitize the provided folder name to ensure it is valid across different operating systems.
|
56
|
+
|
57
|
+
Steps:
|
58
|
+
1. Normalize text to remove accents and special characters.
|
59
|
+
2. Convert to lowercase.
|
60
|
+
3. Replace spaces with underscores.
|
61
|
+
4. Remove invalid characters.
|
62
|
+
5. Strip leading and trailing whitespace.
|
63
|
+
6. Enforce length limit (255 characters).
|
64
|
+
7. Ensure the result contains only valid characters.
|
65
|
+
|
66
|
+
Parameters
|
67
|
+
----------
|
68
|
+
name : str
|
69
|
+
The original folder name to sanitize.
|
70
|
+
|
71
|
+
Returns
|
72
|
+
-------
|
73
|
+
str
|
74
|
+
The sanitized folder name.
|
75
|
+
|
76
|
+
Raises
|
77
|
+
------
|
78
|
+
ValueError
|
79
|
+
If the sanitized folder name is empty or contains invalid characters.
|
80
|
+
"""
|
81
|
+
if not name:
|
82
|
+
self.output.error("Folder name cannot be empty.")
|
83
|
+
|
84
|
+
# Strip leading and trailing whitespace
|
85
|
+
name = name.strip()
|
86
|
+
|
87
|
+
# Normalize to remove accents and special characters
|
88
|
+
name = normalize("NFKD", name).encode("ascii", "ignore").decode("ascii")
|
89
|
+
|
90
|
+
# Convert to lowercase
|
91
|
+
name = name.lower()
|
92
|
+
|
93
|
+
# Replace spaces with underscores
|
94
|
+
name = name.replace(" ", "_")
|
95
|
+
|
96
|
+
# Remove invalid characters for folder names
|
97
|
+
name = re.sub(r'[\\/:*?"<>|]', '', name)
|
98
|
+
|
99
|
+
# Limit the length to 255 characters
|
100
|
+
name = name[:255]
|
101
|
+
|
102
|
+
# Validate against allowed characters
|
103
|
+
if not re.match(r'^[a-z0-9_-]+$', name):
|
104
|
+
self.output.error("The folder name can only contain letters, numbers, underscores, and hyphens.")
|
105
|
+
|
106
|
+
if not name:
|
107
|
+
self.output.error("The sanitized folder name is empty after processing.")
|
108
|
+
|
109
|
+
return name
|
110
|
+
|
111
|
+
def handle(self):
|
112
|
+
"""
|
113
|
+
Executes the setup process for initializing the Orionis project.
|
114
|
+
|
115
|
+
This process includes:
|
116
|
+
1. Cloning the repository.
|
117
|
+
2. Creating a virtual environment.
|
118
|
+
3. Installing dependencies from requirements.txt.
|
119
|
+
4. Setting up the .env file.
|
120
|
+
5. Generating an API key.
|
121
|
+
6. Cleaning up temporary files and .git remote origin.
|
122
|
+
|
123
|
+
Raises
|
124
|
+
------
|
125
|
+
ValueError
|
126
|
+
If there is an error during any subprocess execution.
|
127
|
+
Exception
|
128
|
+
If any unexpected error occurs.
|
129
|
+
"""
|
130
|
+
try:
|
131
|
+
# Validate Folder
|
132
|
+
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.")
|
134
|
+
|
135
|
+
# Clone the repository
|
136
|
+
self.output.info(f"Cloning the repository into '{self.name_app_folder}'... (Getting Latest Version)")
|
137
|
+
subprocess.run(["git", "clone", SKELETON, self.name_app_folder], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
138
|
+
self.output.info(f"Repository successfully cloned into '{self.name_app_folder}'.")
|
139
|
+
|
140
|
+
# Change to the project directory
|
141
|
+
project_path = os.path.join(os.getcwd(), self.name_app_folder)
|
142
|
+
os.chdir(project_path)
|
143
|
+
self.output.info(f"Entering directory '{self.name_app_folder}'.")
|
144
|
+
|
145
|
+
# Create a virtual environment
|
146
|
+
self.output.info("Creating virtual environment...")
|
147
|
+
subprocess.run([sys.executable, "-m", "venv", "venv"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
148
|
+
self.output.info("Virtual environment successfully created.")
|
149
|
+
|
150
|
+
# Virtual environment path
|
151
|
+
venv_path = os.path.join(project_path, "venv", "Scripts" if os.name == "nt" else "bin")
|
152
|
+
|
153
|
+
# Check if requirements.txt exists
|
154
|
+
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}")
|
156
|
+
|
157
|
+
# Install dependencies from requirements.txt
|
158
|
+
self.output.info("Installing dependencies from 'requirements.txt'...")
|
159
|
+
subprocess.run([os.path.join(venv_path, "pip"), "install", "-r", "requirements.txt"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
160
|
+
self.output.info("Dependencies successfully installed.")
|
161
|
+
|
162
|
+
# Create .env
|
163
|
+
example_env_path = os.path.join(project_path, '.env.example')
|
164
|
+
env_path = os.path.join(project_path, '.env')
|
165
|
+
shutil.copy(example_env_path, env_path)
|
166
|
+
|
167
|
+
# Create ApiKey
|
168
|
+
os.chdir(project_path)
|
169
|
+
subprocess.run([sys.executable, '-B', 'reactor', 'key:generate'], capture_output=True, text=True)
|
170
|
+
|
171
|
+
# Remove .git origin
|
172
|
+
subprocess.run(["git", "remote", "remove", "origin"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
173
|
+
|
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
|
+
# Finish Process Message
|
185
|
+
self.output.info(f"Project '{self.name_app_folder}' successfully created at '{os.path.abspath(project_path)}'.")
|
186
|
+
self.output.endInstallation()
|
187
|
+
|
188
|
+
except subprocess.CalledProcessError as e:
|
189
|
+
self.output.error(f"Error while executing command: {e}")
|
190
|
+
except Exception as e:
|
191
|
+
self.output.error(f"An unexpected error occurred: {e}")
|
@@ -0,0 +1,42 @@
|
|
1
|
+
import sys
|
2
|
+
import subprocess
|
3
|
+
from orionis.luminate.installer.output import Output
|
4
|
+
from orionis.luminate.contracts.installer.upgrade_interface import IUpgrade
|
5
|
+
|
6
|
+
class Upgrade(IUpgrade):
|
7
|
+
"""
|
8
|
+
A class responsible for handling the upgrade process of Orionis.
|
9
|
+
|
10
|
+
Methods
|
11
|
+
-------
|
12
|
+
execute() : None
|
13
|
+
Executes the upgrade process to install the latest version of Orionis.
|
14
|
+
"""
|
15
|
+
|
16
|
+
@staticmethod
|
17
|
+
def execute():
|
18
|
+
"""
|
19
|
+
Handle the --upgrade command to update Orionis to the latest version.
|
20
|
+
|
21
|
+
This method attempts to upgrade Orionis using the pip package manager.
|
22
|
+
It executes a command that installs the latest version of Orionis, ensuring
|
23
|
+
the application is up-to-date.
|
24
|
+
|
25
|
+
Raises
|
26
|
+
------
|
27
|
+
ValueError
|
28
|
+
If the upgrade process fails or encounters any error during execution.
|
29
|
+
|
30
|
+
Notes
|
31
|
+
-----
|
32
|
+
The upgrade process uses `pip` via the command:
|
33
|
+
`python -m pip install --upgrade orionis`.
|
34
|
+
"""
|
35
|
+
output = Output()
|
36
|
+
try:
|
37
|
+
output.info("Starting the upgrade process...")
|
38
|
+
subprocess.check_call([sys.executable, "-m", "pip", "install", "--upgrade", "orionis"])
|
39
|
+
except subprocess.CalledProcessError as e:
|
40
|
+
output.error(message=f"Upgrade failed: {e}")
|
41
|
+
except Exception as e:
|
42
|
+
output.error(message=e)
|
File without changes
|
@@ -0,0 +1,116 @@
|
|
1
|
+
import os
|
2
|
+
import logging
|
3
|
+
import threading
|
4
|
+
from pathlib import Path
|
5
|
+
from typing import Optional
|
6
|
+
from orionis.luminate.contracts.log.logger_interface import ILogger
|
7
|
+
|
8
|
+
class Logguer(ILogger):
|
9
|
+
"""
|
10
|
+
A thread-safe singleton logger class for logging messages.
|
11
|
+
|
12
|
+
Attributes
|
13
|
+
----------
|
14
|
+
logger : logging.Logger
|
15
|
+
The logger instance used for logging messages.
|
16
|
+
|
17
|
+
Methods
|
18
|
+
-------
|
19
|
+
info(message: str)
|
20
|
+
Logs an informational message.
|
21
|
+
error(message: str)
|
22
|
+
Logs an error message.
|
23
|
+
success(message: str)
|
24
|
+
Logs a success message (treated as info).
|
25
|
+
warning(message: str)
|
26
|
+
Logs a warning message.
|
27
|
+
debug(message: str)
|
28
|
+
Logs a debug message.
|
29
|
+
"""
|
30
|
+
|
31
|
+
# Singleton instance
|
32
|
+
_instance = None
|
33
|
+
|
34
|
+
# Thread-safe instance creation
|
35
|
+
_lock = threading.Lock()
|
36
|
+
|
37
|
+
def __new__(cls, path: Optional[str] = None, level: int = logging.INFO, filename: Optional[str] = 'orionis.log'):
|
38
|
+
"""
|
39
|
+
Creates or returns the singleton instance of the Logguer class.
|
40
|
+
|
41
|
+
Parameters
|
42
|
+
----------
|
43
|
+
path : str, optional
|
44
|
+
The file path where logs will be stored. If None, a default path is used.
|
45
|
+
level : int, optional
|
46
|
+
The logging level (default is logging.INFO).
|
47
|
+
|
48
|
+
Returns
|
49
|
+
-------
|
50
|
+
Logguer
|
51
|
+
The singleton instance of the logger.
|
52
|
+
"""
|
53
|
+
with cls._lock:
|
54
|
+
if cls._instance is None:
|
55
|
+
cls._instance = super(Logguer, cls).__new__(cls)
|
56
|
+
cls._instance._initialize_logger(path, level, filename)
|
57
|
+
return cls._instance
|
58
|
+
|
59
|
+
def _initialize_logger(self, path: Optional[str], level: int, filename: Optional[str] = 'orionis.log'):
|
60
|
+
"""
|
61
|
+
Initializes the logger with the specified log file path and logging level.
|
62
|
+
|
63
|
+
Parameters
|
64
|
+
----------
|
65
|
+
path : str, optional
|
66
|
+
The file path where logs will be stored.
|
67
|
+
level : int
|
68
|
+
The logging level.
|
69
|
+
"""
|
70
|
+
try:
|
71
|
+
|
72
|
+
if path is None:
|
73
|
+
base_path = Path(os.getcwd())
|
74
|
+
log_dir = base_path / "storage" / "logs"
|
75
|
+
|
76
|
+
# Check if the log directory exists
|
77
|
+
if log_dir.exists():
|
78
|
+
path = log_dir / filename
|
79
|
+
else:
|
80
|
+
path = base_path / filename
|
81
|
+
|
82
|
+
logging.basicConfig(
|
83
|
+
level=level,
|
84
|
+
format="%(asctime)s - %(message)s",
|
85
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
86
|
+
encoding="utf-8",
|
87
|
+
handlers=[
|
88
|
+
logging.FileHandler(path)
|
89
|
+
# logging.StreamHandler()
|
90
|
+
]
|
91
|
+
)
|
92
|
+
|
93
|
+
self.logger = logging.getLogger(__name__)
|
94
|
+
|
95
|
+
except Exception as e:
|
96
|
+
raise RuntimeError(f"Failed to initialize logger: {e}")
|
97
|
+
|
98
|
+
def info(self, message: str) -> None:
|
99
|
+
"""Logs an informational message."""
|
100
|
+
self.logger.info(f"[INFO] - {message}")
|
101
|
+
|
102
|
+
def error(self, message: str) -> None:
|
103
|
+
"""Logs an error message."""
|
104
|
+
self.logger.error(f"[ERROR] - {message}")
|
105
|
+
|
106
|
+
def success(self, message: str) -> None:
|
107
|
+
"""Logs a success message (treated as info)."""
|
108
|
+
self.logger.info(f"[SUCCESS] - {message}")
|
109
|
+
|
110
|
+
def warning(self, message: str) -> None:
|
111
|
+
"""Logs a warning message."""
|
112
|
+
self.logger.warning(f"[WARNING] - {message}")
|
113
|
+
|
114
|
+
def debug(self, message: str) -> None:
|
115
|
+
"""Logs a debug message."""
|
116
|
+
self.logger.debug(f"[DEBUG] - {message}")
|
File without changes
|
@@ -0,0 +1,116 @@
|
|
1
|
+
import argparse
|
2
|
+
from typing import Dict, Any, Optional
|
3
|
+
from orionis.luminate.console.parser import Parser
|
4
|
+
from orionis.luminate.console.cache import CLICache
|
5
|
+
from orionis.luminate.console.base.command import BaseCommand
|
6
|
+
from orionis.luminate.contracts.pipelines.cli_pipeline_interface import ICLIPipeline
|
7
|
+
|
8
|
+
class CLIPipeline(ICLIPipeline):
|
9
|
+
"""
|
10
|
+
Handles the retrieval, parsing, and execution of CLI commands within the Orionis framework.
|
11
|
+
|
12
|
+
This class is responsible for:
|
13
|
+
- Retrieving command metadata from cache.
|
14
|
+
- Parsing command-line arguments dynamically.
|
15
|
+
- Executing the corresponding command with parsed arguments.
|
16
|
+
|
17
|
+
Attributes
|
18
|
+
----------
|
19
|
+
_command : dict
|
20
|
+
Stores the command's metadata, including its instance and expected arguments.
|
21
|
+
_parsed_arguments : argparse.Namespace
|
22
|
+
Holds parsed arguments after processing user input.
|
23
|
+
"""
|
24
|
+
|
25
|
+
def __init__(self):
|
26
|
+
"""
|
27
|
+
Initializes the CLIPipeline instance with an empty command cache
|
28
|
+
and a default argument namespace.
|
29
|
+
"""
|
30
|
+
self._command: Dict[str, Any] = {}
|
31
|
+
self._parsed_arguments: argparse.Namespace = argparse.Namespace()
|
32
|
+
|
33
|
+
def getCommand(self, signature: str) -> "CLIPipeline":
|
34
|
+
"""
|
35
|
+
Retrieves a command from the cache based on its signature.
|
36
|
+
|
37
|
+
Parameters
|
38
|
+
----------
|
39
|
+
signature : str
|
40
|
+
The unique identifier of the command.
|
41
|
+
|
42
|
+
Returns
|
43
|
+
-------
|
44
|
+
CLIPipeline
|
45
|
+
The current instance of CLIPipeline for method chaining.
|
46
|
+
|
47
|
+
Raises
|
48
|
+
------
|
49
|
+
ValueError
|
50
|
+
If the command signature is not found in the cache.
|
51
|
+
"""
|
52
|
+
try:
|
53
|
+
cache = CLICache().getCommands()
|
54
|
+
self._command = cache.get(signature)
|
55
|
+
return self
|
56
|
+
except KeyError as e:
|
57
|
+
raise ValueError(e)
|
58
|
+
|
59
|
+
def parseArguments(self, vars: Optional[Dict[str, Any]] = None, *args, **kwargs) -> "CLIPipeline":
|
60
|
+
"""
|
61
|
+
Parses command-line arguments using the Orionis argument parser.
|
62
|
+
|
63
|
+
Parameters
|
64
|
+
----------
|
65
|
+
vars : dict, optional
|
66
|
+
A dictionary of predefined variables to be included in parsing.
|
67
|
+
*args
|
68
|
+
Positional arguments for the parser.
|
69
|
+
**kwargs
|
70
|
+
Keyword arguments for the parser.
|
71
|
+
|
72
|
+
Returns
|
73
|
+
-------
|
74
|
+
CLIPipeline
|
75
|
+
The current instance of CLIPipeline for method chaining.
|
76
|
+
|
77
|
+
Raises
|
78
|
+
------
|
79
|
+
ValueError
|
80
|
+
If an error occurs during argument parsing.
|
81
|
+
"""
|
82
|
+
try:
|
83
|
+
arguments = self._command.get("arguments")
|
84
|
+
if arguments:
|
85
|
+
arg_parser = Parser(vars=vars or {}, args=args, kwargs=kwargs)
|
86
|
+
arg_parser.setArguments(arguments=arguments)
|
87
|
+
arg_parser.recognize()
|
88
|
+
self._parsed_arguments = arg_parser.get()
|
89
|
+
|
90
|
+
return self
|
91
|
+
|
92
|
+
except Exception as e:
|
93
|
+
raise ValueError(f"Error parsing arguments: {e}")
|
94
|
+
|
95
|
+
def execute(self) -> Any:
|
96
|
+
"""
|
97
|
+
Executes the retrieved command using parsed arguments.
|
98
|
+
|
99
|
+
This method:
|
100
|
+
- Instantiates the command class.
|
101
|
+
- Calls the `handle()` method, passing the parsed arguments.
|
102
|
+
|
103
|
+
Returns
|
104
|
+
-------
|
105
|
+
Any
|
106
|
+
The output of the command execution.
|
107
|
+
|
108
|
+
Raises
|
109
|
+
------
|
110
|
+
ValueError
|
111
|
+
If the command instance is invalid.
|
112
|
+
"""
|
113
|
+
command_class = self._command.get("instance")
|
114
|
+
command_instance: BaseCommand = command_class()
|
115
|
+
command_instance.setArgs(self._parsed_arguments)
|
116
|
+
return command_instance.handle(**vars(self._parsed_arguments))
|
File without changes
|
@@ -0,0 +1,206 @@
|
|
1
|
+
import os
|
2
|
+
import sys
|
3
|
+
import shutil
|
4
|
+
import subprocess
|
5
|
+
from orionis.framework import VERSION
|
6
|
+
from orionis.luminate.console.output.console import Console
|
7
|
+
from orionis.luminate.contracts.publisher.pypi_publisher_interface import IPypiPublisher
|
8
|
+
|
9
|
+
class PypiPublisher(IPypiPublisher):
|
10
|
+
"""
|
11
|
+
Handles the publishing process of a package to PyPI and repository management.
|
12
|
+
|
13
|
+
This class automates the process of committing changes to a Git repository, building a Python package,
|
14
|
+
uploading it to PyPI, and cleaning up temporary files. It requires a PyPI authentication token.
|
15
|
+
|
16
|
+
Methods
|
17
|
+
-------
|
18
|
+
__init__(token: str = None)
|
19
|
+
Initializes the class with an optional PyPI authentication token.
|
20
|
+
|
21
|
+
gitPush()
|
22
|
+
Commits and pushes changes to the Git repository if modifications are detected.
|
23
|
+
|
24
|
+
build()
|
25
|
+
Compiles the package using `setup.py` to generate distribution files.
|
26
|
+
|
27
|
+
publish()
|
28
|
+
Uploads the package to PyPI using Twine.
|
29
|
+
|
30
|
+
clearRepository()
|
31
|
+
Deletes temporary directories created during the publishing process.
|
32
|
+
"""
|
33
|
+
|
34
|
+
def __init__(self, token: str = None):
|
35
|
+
"""
|
36
|
+
Initializes the class with an authentication token.
|
37
|
+
|
38
|
+
Parameters
|
39
|
+
----------
|
40
|
+
token : str, optional
|
41
|
+
Authentication token for PyPI. If not provided, it is retrieved from environment variables.
|
42
|
+
|
43
|
+
Attributes
|
44
|
+
----------
|
45
|
+
token : str
|
46
|
+
The PyPI authentication token.
|
47
|
+
python_path : str
|
48
|
+
The path to the Python interpreter used in the environment.
|
49
|
+
project_root : str
|
50
|
+
The root directory of the project where the process is executed.
|
51
|
+
"""
|
52
|
+
self.token = token or os.getenv("PYPI_TOKEN").strip()
|
53
|
+
self.python_path = sys.executable
|
54
|
+
self.project_root = os.getcwd()
|
55
|
+
self.clearRepository()
|
56
|
+
Console.clear()
|
57
|
+
Console.newLine()
|
58
|
+
|
59
|
+
def gitPush(self):
|
60
|
+
"""
|
61
|
+
Commits and pushes changes to the Git repository if there are modifications.
|
62
|
+
|
63
|
+
This method checks for uncommitted changes and stages, commits, and pushes them
|
64
|
+
to the remote Git repository.
|
65
|
+
|
66
|
+
If there are no changes, it logs a message indicating no commits are necessary.
|
67
|
+
|
68
|
+
Raises
|
69
|
+
------
|
70
|
+
subprocess.CalledProcessError
|
71
|
+
If any of the subprocess calls to Git fail.
|
72
|
+
"""
|
73
|
+
subprocess.run(
|
74
|
+
["git", "rm", "-r", "--cached", "."], capture_output=True, text=True, cwd=self.project_root
|
75
|
+
)
|
76
|
+
|
77
|
+
git_status = subprocess.run(
|
78
|
+
["git", "status", "--short"], capture_output=True, text=True, cwd=self.project_root
|
79
|
+
)
|
80
|
+
modified_files = git_status.stdout.strip()
|
81
|
+
|
82
|
+
if modified_files:
|
83
|
+
Console.info("๐ Staging files for commit...")
|
84
|
+
subprocess.run(
|
85
|
+
["git", "add", "."], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, cwd=self.project_root
|
86
|
+
)
|
87
|
+
|
88
|
+
Console.info(f"โ
Committing changes: '๐ฆ Release version {VERSION}'")
|
89
|
+
subprocess.run(
|
90
|
+
["git", "commit", "-m", f"๐ฆ Release version {VERSION}"], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, cwd=self.project_root
|
91
|
+
)
|
92
|
+
|
93
|
+
Console.info("๐ Pushing changes to the remote repository...")
|
94
|
+
subprocess.run(
|
95
|
+
["git", "push", "-f"], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, cwd=self.project_root
|
96
|
+
)
|
97
|
+
else:
|
98
|
+
Console.info("โ
No changes to commit.")
|
99
|
+
|
100
|
+
def build(self):
|
101
|
+
"""
|
102
|
+
Compiles the package using `setup.py` to generate distribution files.
|
103
|
+
|
104
|
+
This method runs the `setup.py` script to generate both source (`sdist`)
|
105
|
+
and wheel (`bdist_wheel`) distribution formats for the package.
|
106
|
+
|
107
|
+
Raises
|
108
|
+
------
|
109
|
+
subprocess.CalledProcessError
|
110
|
+
If the `setup.py` command fails.
|
111
|
+
"""
|
112
|
+
try:
|
113
|
+
Console.info("๐ ๏ธ Building the package...")
|
114
|
+
|
115
|
+
setup_path = os.path.join(self.project_root, "setup.py")
|
116
|
+
if not os.path.exists(setup_path):
|
117
|
+
Console.error("โ Error: setup.py not found in the current execution directory.")
|
118
|
+
return
|
119
|
+
|
120
|
+
subprocess.run(
|
121
|
+
[self.python_path, "setup.py", "sdist", "bdist_wheel"], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, cwd=self.project_root
|
122
|
+
)
|
123
|
+
|
124
|
+
Console.info("โ
Build process completed successfully!")
|
125
|
+
except subprocess.CalledProcessError as e:
|
126
|
+
Console.error(f"โ Build failed: {e}")
|
127
|
+
|
128
|
+
def publish(self):
|
129
|
+
"""
|
130
|
+
Uploads the package to PyPI using Twine.
|
131
|
+
|
132
|
+
This method uses the `twine` command to upload the built distribution files
|
133
|
+
from the `dist/` folder to the PyPI repository.
|
134
|
+
|
135
|
+
Parameters
|
136
|
+
----------
|
137
|
+
token : str
|
138
|
+
The PyPI authentication token, which is passed during the initialization.
|
139
|
+
|
140
|
+
Raises
|
141
|
+
------
|
142
|
+
subprocess.CalledProcessError
|
143
|
+
If the Twine command fails during the upload process.
|
144
|
+
ValueError
|
145
|
+
If no token is provided for authentication.
|
146
|
+
"""
|
147
|
+
token = self.token
|
148
|
+
if not token:
|
149
|
+
Console.error("โ Error: PyPI token not found in environment variables.")
|
150
|
+
return
|
151
|
+
|
152
|
+
twine_path = os.path.join(self.project_root, 'venv', 'Scripts', 'twine')
|
153
|
+
twine_path = os.path.abspath(twine_path)
|
154
|
+
|
155
|
+
Console.info("๐ค Uploading package to PyPI...")
|
156
|
+
try:
|
157
|
+
subprocess.run(
|
158
|
+
[twine_path, "upload", "dist/*", "-u", "__token__", "-p", token],
|
159
|
+
check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, cwd=self.project_root
|
160
|
+
)
|
161
|
+
except Exception as e:
|
162
|
+
Console.fail(f"๐ด Error loading the package. Try changing the version and retry. Error: {e}")
|
163
|
+
Console.warning("โ If the issue persists, review the script in detail.")
|
164
|
+
exit()
|
165
|
+
|
166
|
+
Console.info("๐งน Cleaning up temporary files...")
|
167
|
+
subprocess.run(
|
168
|
+
["powershell", "-Command", "Get-ChildItem -Recurse -Filter *.pyc | Remove-Item; Get-ChildItem -Recurse -Filter __pycache__ | Remove-Item -Recurse"],
|
169
|
+
check=True, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, cwd=self.project_root
|
170
|
+
)
|
171
|
+
self.clearRepository()
|
172
|
+
Console.success(f"โ
[v{VERSION}] - Publishing process completed successfully!")
|
173
|
+
Console.newLine()
|
174
|
+
|
175
|
+
def clearRepository(self):
|
176
|
+
"""
|
177
|
+
Deletes temporary directories created during the publishing process.
|
178
|
+
|
179
|
+
This method removes the following directories from the project root:
|
180
|
+
- `build/`
|
181
|
+
- `dist/`
|
182
|
+
- `orionis.egg-info/`
|
183
|
+
|
184
|
+
Raises
|
185
|
+
------
|
186
|
+
PermissionError
|
187
|
+
If the method fails to delete any of the directories due to insufficient permissions.
|
188
|
+
Exception
|
189
|
+
If any other error occurs during the deletion process.
|
190
|
+
"""
|
191
|
+
|
192
|
+
# Remove the log file if it exists
|
193
|
+
if os.path.exists('orionis.log'):
|
194
|
+
os.remove('orionis.log')
|
195
|
+
|
196
|
+
# Remove the build, dist, and egg-info directories
|
197
|
+
folders = ["build", "dist", "orionis.egg-info"]
|
198
|
+
for folder in folders:
|
199
|
+
folder_path = os.path.join(self.project_root, folder)
|
200
|
+
if os.path.exists(folder_path):
|
201
|
+
try:
|
202
|
+
shutil.rmtree(folder_path)
|
203
|
+
except PermissionError:
|
204
|
+
Console.error(f"โ Error: Could not remove {folder_path} due to insufficient permissions.")
|
205
|
+
except Exception as e:
|
206
|
+
Console.error(f"โ Error removing {folder_path}: {str(e)}")
|
Binary file
|
File without changes
|