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.
Files changed (133) hide show
  1. orionis/__init__.py +0 -0
  2. orionis/cli_manager.py +48 -0
  3. orionis/framework.py +45 -0
  4. orionis/luminate/__init__.py +0 -0
  5. orionis/luminate/app.py +0 -0
  6. orionis/luminate/bootstrap/__init__.py +0 -0
  7. orionis/luminate/bootstrap/parser.py +49 -0
  8. orionis/luminate/bootstrap/register.py +95 -0
  9. orionis/luminate/cache/__init__.py +0 -0
  10. orionis/luminate/cache/app/__init__.py +0 -0
  11. orionis/luminate/cache/app/config.py +96 -0
  12. orionis/luminate/cache/console/__init__.py +0 -0
  13. orionis/luminate/cache/console/commands.py +98 -0
  14. orionis/luminate/config/__init__.py +0 -0
  15. orionis/luminate/config/dataclass/__init__.py +0 -0
  16. orionis/luminate/config/dataclass/app.py +50 -0
  17. orionis/luminate/config/dataclass/auth.py +17 -0
  18. orionis/luminate/config/dataclass/cache.py +51 -0
  19. orionis/luminate/config/dataclass/cors.py +58 -0
  20. orionis/luminate/config/dataclass/database.py +203 -0
  21. orionis/luminate/config/dataclass/filesystems.py +102 -0
  22. orionis/luminate/config/dataclass/logging.py +107 -0
  23. orionis/luminate/config/dataclass/mail.py +81 -0
  24. orionis/luminate/config/dataclass/queue.py +63 -0
  25. orionis/luminate/config/dataclass/session.py +59 -0
  26. orionis/luminate/config/environment.py +110 -0
  27. orionis/luminate/config/sections.py +37 -0
  28. orionis/luminate/console/__init__.py +0 -0
  29. orionis/luminate/console/base/__init__.py +0 -0
  30. orionis/luminate/console/base/command.py +427 -0
  31. orionis/luminate/console/cache.py +132 -0
  32. orionis/luminate/console/command.py +40 -0
  33. orionis/luminate/console/command_filter.py +40 -0
  34. orionis/luminate/console/commands/__init__.py +0 -0
  35. orionis/luminate/console/commands/cache_clear.py +56 -0
  36. orionis/luminate/console/commands/help.py +59 -0
  37. orionis/luminate/console/commands/schedule_work.py +50 -0
  38. orionis/luminate/console/commands/tests.py +40 -0
  39. orionis/luminate/console/commands/version.py +39 -0
  40. orionis/luminate/console/exceptions/__init__.py +0 -0
  41. orionis/luminate/console/exceptions/cli_exception.py +125 -0
  42. orionis/luminate/console/kernel.py +32 -0
  43. orionis/luminate/console/output/__init__.py +0 -0
  44. orionis/luminate/console/output/console.py +426 -0
  45. orionis/luminate/console/output/executor.py +90 -0
  46. orionis/luminate/console/output/progress_bar.py +100 -0
  47. orionis/luminate/console/output/styles.py +95 -0
  48. orionis/luminate/console/parser.py +159 -0
  49. orionis/luminate/console/register.py +98 -0
  50. orionis/luminate/console/runner.py +126 -0
  51. orionis/luminate/console/scripts/__init__.py +0 -0
  52. orionis/luminate/console/scripts/management.py +94 -0
  53. orionis/luminate/console/tasks/__init__.py +0 -0
  54. orionis/luminate/console/tasks/scheduler.py +616 -0
  55. orionis/luminate/contracts/__init__.py +0 -0
  56. orionis/luminate/contracts/bootstrap/parser_interface.py +46 -0
  57. orionis/luminate/contracts/cache/__init__.py +0 -0
  58. orionis/luminate/contracts/cache/cache_commands_interface.py +69 -0
  59. orionis/luminate/contracts/config/__init__.py +0 -0
  60. orionis/luminate/contracts/config/config_interface.py +27 -0
  61. orionis/luminate/contracts/config/environment_interface.py +64 -0
  62. orionis/luminate/contracts/console/__init__.py +0 -0
  63. orionis/luminate/contracts/console/base_command_interface.py +448 -0
  64. orionis/luminate/contracts/console/cli_cache_interface.py +34 -0
  65. orionis/luminate/contracts/console/command_filter_interface.py +32 -0
  66. orionis/luminate/contracts/console/command_interface.py +36 -0
  67. orionis/luminate/contracts/console/console_interface.py +305 -0
  68. orionis/luminate/contracts/console/executor_interface.py +51 -0
  69. orionis/luminate/contracts/console/kernel_interface.py +32 -0
  70. orionis/luminate/contracts/console/management_interface.py +63 -0
  71. orionis/luminate/contracts/console/parser_interface.py +76 -0
  72. orionis/luminate/contracts/console/progress_bar_interface.py +66 -0
  73. orionis/luminate/contracts/console/register_interface.py +32 -0
  74. orionis/luminate/contracts/console/runner_interface.py +50 -0
  75. orionis/luminate/contracts/console/schedule_interface.py +317 -0
  76. orionis/luminate/contracts/console/task_manager_interface.py +37 -0
  77. orionis/luminate/contracts/facades/__init__.py +0 -0
  78. orionis/luminate/contracts/facades/env_interface.py +64 -0
  79. orionis/luminate/contracts/facades/log_interface.py +48 -0
  80. orionis/luminate/contracts/facades/paths_interface.py +141 -0
  81. orionis/luminate/contracts/facades/tests_interface.py +33 -0
  82. orionis/luminate/contracts/files/__init__.py +0 -0
  83. orionis/luminate/contracts/files/paths_interface.py +29 -0
  84. orionis/luminate/contracts/installer/__init__.py +0 -0
  85. orionis/luminate/contracts/installer/output_interface.py +125 -0
  86. orionis/luminate/contracts/installer/setup_interface.py +29 -0
  87. orionis/luminate/contracts/installer/upgrade_interface.py +24 -0
  88. orionis/luminate/contracts/log/__init__.py +0 -0
  89. orionis/luminate/contracts/log/logger_interface.py +33 -0
  90. orionis/luminate/contracts/pipelines/cli_pipeline_interface.py +84 -0
  91. orionis/luminate/contracts/publisher/__init__.py +0 -0
  92. orionis/luminate/contracts/publisher/pypi_publisher_interface.py +36 -0
  93. orionis/luminate/contracts/test/__init__.py +0 -0
  94. orionis/luminate/contracts/test/unit_test_interface.py +51 -0
  95. orionis/luminate/contracts/tools/__init__.py +0 -0
  96. orionis/luminate/contracts/tools/reflection_interface.py +343 -0
  97. orionis/luminate/contracts/tools/std_interface.py +56 -0
  98. orionis/luminate/facades/__init__.py +0 -0
  99. orionis/luminate/facades/environment.py +81 -0
  100. orionis/luminate/facades/log.py +56 -0
  101. orionis/luminate/facades/paths.py +268 -0
  102. orionis/luminate/facades/tests.py +48 -0
  103. orionis/luminate/files/__init__.py +0 -0
  104. orionis/luminate/files/paths.py +56 -0
  105. orionis/luminate/installer/__init__.py +0 -0
  106. orionis/luminate/installer/icon.ascii +11 -0
  107. orionis/luminate/installer/info.ascii +13 -0
  108. orionis/luminate/installer/output.py +188 -0
  109. orionis/luminate/installer/setup.py +191 -0
  110. orionis/luminate/installer/upgrade.py +42 -0
  111. orionis/luminate/log/__init__.py +0 -0
  112. orionis/luminate/log/logger.py +116 -0
  113. orionis/luminate/pipelines/__init__.py +0 -0
  114. orionis/luminate/pipelines/cli_pipeline.py +116 -0
  115. orionis/luminate/publisher/__init__.py +0 -0
  116. orionis/luminate/publisher/pypi.py +206 -0
  117. orionis/luminate/static/logos/flaskavel.png +0 -0
  118. orionis/luminate/test/__init__.py +0 -0
  119. orionis/luminate/test/exception.py +48 -0
  120. orionis/luminate/test/unit_test.py +108 -0
  121. orionis/luminate/tools/__init__.py +0 -0
  122. orionis/luminate/tools/reflection.py +390 -0
  123. orionis/luminate/tools/std.py +53 -0
  124. orionis-0.1.0.dist-info/LICENCE +21 -0
  125. orionis-0.1.0.dist-info/METADATA +27 -0
  126. orionis-0.1.0.dist-info/RECORD +133 -0
  127. orionis-0.1.0.dist-info/WHEEL +5 -0
  128. orionis-0.1.0.dist-info/entry_points.txt +2 -0
  129. orionis-0.1.0.dist-info/top_level.txt +2 -0
  130. tests/__init__.py +0 -0
  131. tests/tools/__init__.py +0 -0
  132. tests/tools/class_example.py +50 -0
  133. 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)}")
File without changes