aneSettings 2025.9.0__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 - Cybernetic Innovations
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,202 @@
1
+ Metadata-Version: 2.4
2
+ Name: aneSettings
3
+ Version: 2025.9.0
4
+ Summary: High-performance secure core framework for scalable, reliable applications
5
+ Author-email: Cybernetic Innovations <github@cyberneticinnovations.com>
6
+ Classifier: License :: OSI Approved :: MIT License
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: PyYAML~=6.0.2
11
+ Requires-Dist: cryptography~=45.0.3
12
+ Requires-Dist: loguru~=0.7.3
13
+ Requires-Dist: pydantic~=2.11.5
14
+ Dynamic: license-file
15
+
16
+
17
+ # aneSettings
18
+
19
+ aneSettings is a framework for developing scalable applications. It offers a set of core libraries and utilities to streamline the development workflow, enforce security best practices, and improve code maintainability. By supporting modern programming paradigms, it allows developers to build robust and efficient solutions with reduced overhead.
20
+
21
+ ### Key Features:
22
+ - Security-First Approach: Built-in security measures to prevent common vulnerabilities.
23
+ - Scalability: Optimized to handle high loads and large-scale applications.
24
+ - Modularity: Highly modular architecture, allowing seamless integration and customization.
25
+ - Ease of Use: Developer-friendly APIs and comprehensive documentation.
26
+
27
+ ### Goals:
28
+ - Accelerate development time while maintaining high code quality.
29
+ - Provide a flexible foundation to meet diverse application needs.
30
+ - Ensure application security without compromising performance.
31
+
32
+ ---
33
+
34
+ ### Core Components:
35
+ - **ConfigSettings**: Handles configuration and settings management
36
+ - **CustomLogging**: Provides customized logging functionality
37
+ - **Encryption**: Offers encryption-related utilities
38
+ - **ProjectRoot**: Manages project root detection and path resolution
39
+
40
+ ---
41
+
42
+ ### **_ConfigSettings:_**
43
+
44
+ ```python
45
+ # Basic Logging and Config Settings
46
+ from aneSettings import logger, config
47
+
48
+ # Constants
49
+ FORMAT_PADDING = 25
50
+ SEPARATOR_LINE = "-" * 150
51
+
52
+
53
+ def log_sorted_settings(system_settings):
54
+ """Logs the key-value pairs of sorted settings."""
55
+ for setting_name, setting_value in system_settings:
56
+ logger.info(f'{setting_name:>{FORMAT_PADDING}}: {setting_value}')
57
+
58
+
59
+ if __name__ == '__main__':
60
+ # -------------------------------------------------------------------------------------------------
61
+ # Example of viewing and sorting configuration settings
62
+ # -------------------------------------------------------------------------------------------------
63
+ logger.info(SEPARATOR_LINE)
64
+ sorted_settings = sorted(config)
65
+ log_sorted_settings(sorted_settings)
66
+ ```
67
+
68
+ #### **_output_**
69
+
70
+ ```shell
71
+ aneSettings | INFO | ------------------------------------------------------------------------------------------------------------------------------------------------------
72
+ aneSettings | INFO | ENVIRONMENT: Local
73
+ aneSettings | INFO | FN_KEY: pit...-iM2c=
74
+ aneSettings | INFO | LOG_APPNAME: aneSettings
75
+ aneSettings | INFO | LOG_FORMAT: {extra[app]} | <level>{level: <8}</level> | <cyan><level>{message}</level></cyan>
76
+ aneSettings | INFO | LOG_LEVEL: DEBUG
77
+ aneSettings | INFO | MSSQL_DATABASE: {default_database}
78
+ aneSettings | INFO | MSSQL_HOSTNAME: {hostname}
79
+ aneSettings | INFO | MSSQL_PASSWORD: {password}
80
+ aneSettings | INFO | MSSQL_PORT: {port}
81
+ aneSettings | INFO | MSSQL_TRUST: {trust}
82
+ aneSettings | INFO | MSSQL_USERNAME: {username}
83
+ aneSettings | INFO | PROJECT_ROOT: /{project_root}/aneSettings
84
+ aneSettings | INFO | VERSION_CORE: 2025.9.0
85
+ ```
86
+
87
+ ---
88
+
89
+ ### **_Encryption:_**
90
+
91
+ ```python
92
+ # Basic Logging and Encryption
93
+ from aneSettings import logger, encryption_service
94
+
95
+ # Constants
96
+ FORMAT_PADDING = 25
97
+ SEPARATOR_LINE = "-" * 150
98
+
99
+
100
+ def log_formatted(key, value):
101
+ """Helper to standardize log output."""
102
+ logger.info(f'{key:>{FORMAT_PADDING}}: {value}')
103
+
104
+
105
+ if __name__ == '__main__':
106
+ # -------------------------------------------------------------------------------------------------
107
+ # Example of Encryption Usage
108
+ # -------------------------------------------------------------------------------------------------
109
+
110
+ # Data: set and show values
111
+ logger.info(SEPARATOR_LINE)
112
+ secret_data = "Sensitive Information"
113
+ log_formatted(key="Data", value=secret_data)
114
+ encryption_key = encryption_service.key.decode()
115
+ log_formatted(key="Key", value=encryption_key)
116
+
117
+ # Encryption: encrypt and decrypt
118
+ logger.info(SEPARATOR_LINE)
119
+ encrypted = encryption_service.encrypt(secret_data)
120
+ log_formatted(key="Encryption successful", value=f"{encrypted != secret_data} - {encrypted.decode()}")
121
+
122
+ decrypted = encryption_service.decrypt(encrypted)
123
+ log_formatted(key="Decryption successful", value=f"{decrypted == secret_data} - {decrypted}")
124
+
125
+ # Base64: encode and decode
126
+ logger.info(SEPARATOR_LINE)
127
+ b64_encoded = encryption_service.base64_encode(secret_data)
128
+ log_formatted(key="Encode successful", value=f"{b64_encoded != secret_data} - {b64_encoded}")
129
+
130
+ b64_decoded = encryption_service.base64_decode(b64_encoded)
131
+ log_formatted(key="Decode successful", value=f"{b64_decoded == secret_data} - {b64_decoded}")
132
+
133
+ # Generate a new key to replace the one in .env.settings `fn_key`
134
+ logger.info(SEPARATOR_LINE)
135
+ new_fn_key = encryption_service.generate_fernet_key().decode()
136
+ log_formatted(key="New fn_key", value=f"{new_fn_key}")
137
+ log_formatted(key="", value="Use this key to replace the one in .env.settings `fn_key`")
138
+
139
+ logger.info(SEPARATOR_LINE)
140
+ ```
141
+
142
+ #### **_output_**
143
+
144
+ ```shell
145
+ aneSettings | INFO | ------------------------------------------------------------------------------------------------------------------------------------------------------
146
+ aneSettings | INFO | Data: Sensitive Information
147
+ aneSettings | INFO | Key: pitANnjVW1OX2LuVqrWw1H2b69wCewmdARQzr6-iM2c=
148
+ aneSettings | INFO | ------------------------------------------------------------------------------------------------------------------------------------------------------
149
+ aneSettings | INFO | Encryption successful: True - gAAAAABox0DLjm7IOmFNRio8FYnp5tLVtMqPFpx5qFbbeot_jIUNah8XqLqHhPNmvaw1HpIBe0ebsna7ou8BrVnQ9erv6Fr1VK_O7PC3xDDXQXSEnyA2WhE=
150
+ aneSettings | INFO | Decryption successful: True - Sensitive Information
151
+ aneSettings | INFO | ------------------------------------------------------------------------------------------------------------------------------------------------------
152
+ aneSettings | INFO | Encode successful: True - U2Vuc2l0aXZlIEluZm9ybWF0aW9u
153
+ aneSettings | INFO | Decode successful: True - Sensitive Information
154
+ aneSettings | INFO | ------------------------------------------------------------------------------------------------------------------------------------------------------
155
+ aneSettings | INFO | New fn_key: RAl2XHZUwXQyZwXdzwVWJGKDDSwyJluh41De9KHw9oI=
156
+ aneSettings | INFO | : Use this key to replace the one in .env.settings `fn_key`
157
+ ```
158
+
159
+ ---
160
+
161
+ ### **_ProjectRoot:_**
162
+
163
+ ```python
164
+ # Basic Logging and Project Path Operations
165
+ from aneSettings import logger, project_root
166
+
167
+ # Constants
168
+ FORMAT_PADDING = 25
169
+ SEPARATOR_LINE = "-" * 150
170
+
171
+
172
+ def log_formatted(key, value):
173
+ """Helper to standardize log output."""
174
+ logger.info(f'{key:>{FORMAT_PADDING}}: {value}')
175
+
176
+
177
+ if __name__ == "__main__":
178
+ # -------------------------------------------------------------------------------------------------
179
+ # Example of Project Path Operations
180
+ # -------------------------------------------------------------------------------------------------
181
+
182
+ # Get project-related paths
183
+ root_path = project_root
184
+ config_path = project_root / "config"
185
+ data_path = project_root / "data"
186
+
187
+ logger.info(SEPARATOR_LINE)
188
+ log_formatted(key="Project root", value=root_path)
189
+ log_formatted(key="Config path", value=config_path)
190
+ log_formatted(key="Data path", value=data_path)
191
+ logger.info(SEPARATOR_LINE)
192
+ ```
193
+
194
+ #### **_output_**
195
+
196
+ ```shell
197
+ aneSettings | INFO | ------------------------------------------------------------------------------------------------------------------------------------------------------
198
+ aneSettings | INFO | Project root: {project_root}
199
+ aneSettings | INFO | Config path: {project_root}/config
200
+ aneSettings | INFO | Data path: {project_root}/data
201
+ aneSettings | INFO | ------------------------------------------------------------------------------------------------------------------------------------------------------
202
+ ```
@@ -0,0 +1,187 @@
1
+
2
+ # aneSettings
3
+
4
+ aneSettings is a framework for developing scalable applications. It offers a set of core libraries and utilities to streamline the development workflow, enforce security best practices, and improve code maintainability. By supporting modern programming paradigms, it allows developers to build robust and efficient solutions with reduced overhead.
5
+
6
+ ### Key Features:
7
+ - Security-First Approach: Built-in security measures to prevent common vulnerabilities.
8
+ - Scalability: Optimized to handle high loads and large-scale applications.
9
+ - Modularity: Highly modular architecture, allowing seamless integration and customization.
10
+ - Ease of Use: Developer-friendly APIs and comprehensive documentation.
11
+
12
+ ### Goals:
13
+ - Accelerate development time while maintaining high code quality.
14
+ - Provide a flexible foundation to meet diverse application needs.
15
+ - Ensure application security without compromising performance.
16
+
17
+ ---
18
+
19
+ ### Core Components:
20
+ - **ConfigSettings**: Handles configuration and settings management
21
+ - **CustomLogging**: Provides customized logging functionality
22
+ - **Encryption**: Offers encryption-related utilities
23
+ - **ProjectRoot**: Manages project root detection and path resolution
24
+
25
+ ---
26
+
27
+ ### **_ConfigSettings:_**
28
+
29
+ ```python
30
+ # Basic Logging and Config Settings
31
+ from aneSettings import logger, config
32
+
33
+ # Constants
34
+ FORMAT_PADDING = 25
35
+ SEPARATOR_LINE = "-" * 150
36
+
37
+
38
+ def log_sorted_settings(system_settings):
39
+ """Logs the key-value pairs of sorted settings."""
40
+ for setting_name, setting_value in system_settings:
41
+ logger.info(f'{setting_name:>{FORMAT_PADDING}}: {setting_value}')
42
+
43
+
44
+ if __name__ == '__main__':
45
+ # -------------------------------------------------------------------------------------------------
46
+ # Example of viewing and sorting configuration settings
47
+ # -------------------------------------------------------------------------------------------------
48
+ logger.info(SEPARATOR_LINE)
49
+ sorted_settings = sorted(config)
50
+ log_sorted_settings(sorted_settings)
51
+ ```
52
+
53
+ #### **_output_**
54
+
55
+ ```shell
56
+ aneSettings | INFO | ------------------------------------------------------------------------------------------------------------------------------------------------------
57
+ aneSettings | INFO | ENVIRONMENT: Local
58
+ aneSettings | INFO | FN_KEY: pit...-iM2c=
59
+ aneSettings | INFO | LOG_APPNAME: aneSettings
60
+ aneSettings | INFO | LOG_FORMAT: {extra[app]} | <level>{level: <8}</level> | <cyan><level>{message}</level></cyan>
61
+ aneSettings | INFO | LOG_LEVEL: DEBUG
62
+ aneSettings | INFO | MSSQL_DATABASE: {default_database}
63
+ aneSettings | INFO | MSSQL_HOSTNAME: {hostname}
64
+ aneSettings | INFO | MSSQL_PASSWORD: {password}
65
+ aneSettings | INFO | MSSQL_PORT: {port}
66
+ aneSettings | INFO | MSSQL_TRUST: {trust}
67
+ aneSettings | INFO | MSSQL_USERNAME: {username}
68
+ aneSettings | INFO | PROJECT_ROOT: /{project_root}/aneSettings
69
+ aneSettings | INFO | VERSION_CORE: 2025.9.0
70
+ ```
71
+
72
+ ---
73
+
74
+ ### **_Encryption:_**
75
+
76
+ ```python
77
+ # Basic Logging and Encryption
78
+ from aneSettings import logger, encryption_service
79
+
80
+ # Constants
81
+ FORMAT_PADDING = 25
82
+ SEPARATOR_LINE = "-" * 150
83
+
84
+
85
+ def log_formatted(key, value):
86
+ """Helper to standardize log output."""
87
+ logger.info(f'{key:>{FORMAT_PADDING}}: {value}')
88
+
89
+
90
+ if __name__ == '__main__':
91
+ # -------------------------------------------------------------------------------------------------
92
+ # Example of Encryption Usage
93
+ # -------------------------------------------------------------------------------------------------
94
+
95
+ # Data: set and show values
96
+ logger.info(SEPARATOR_LINE)
97
+ secret_data = "Sensitive Information"
98
+ log_formatted(key="Data", value=secret_data)
99
+ encryption_key = encryption_service.key.decode()
100
+ log_formatted(key="Key", value=encryption_key)
101
+
102
+ # Encryption: encrypt and decrypt
103
+ logger.info(SEPARATOR_LINE)
104
+ encrypted = encryption_service.encrypt(secret_data)
105
+ log_formatted(key="Encryption successful", value=f"{encrypted != secret_data} - {encrypted.decode()}")
106
+
107
+ decrypted = encryption_service.decrypt(encrypted)
108
+ log_formatted(key="Decryption successful", value=f"{decrypted == secret_data} - {decrypted}")
109
+
110
+ # Base64: encode and decode
111
+ logger.info(SEPARATOR_LINE)
112
+ b64_encoded = encryption_service.base64_encode(secret_data)
113
+ log_formatted(key="Encode successful", value=f"{b64_encoded != secret_data} - {b64_encoded}")
114
+
115
+ b64_decoded = encryption_service.base64_decode(b64_encoded)
116
+ log_formatted(key="Decode successful", value=f"{b64_decoded == secret_data} - {b64_decoded}")
117
+
118
+ # Generate a new key to replace the one in .env.settings `fn_key`
119
+ logger.info(SEPARATOR_LINE)
120
+ new_fn_key = encryption_service.generate_fernet_key().decode()
121
+ log_formatted(key="New fn_key", value=f"{new_fn_key}")
122
+ log_formatted(key="", value="Use this key to replace the one in .env.settings `fn_key`")
123
+
124
+ logger.info(SEPARATOR_LINE)
125
+ ```
126
+
127
+ #### **_output_**
128
+
129
+ ```shell
130
+ aneSettings | INFO | ------------------------------------------------------------------------------------------------------------------------------------------------------
131
+ aneSettings | INFO | Data: Sensitive Information
132
+ aneSettings | INFO | Key: pitANnjVW1OX2LuVqrWw1H2b69wCewmdARQzr6-iM2c=
133
+ aneSettings | INFO | ------------------------------------------------------------------------------------------------------------------------------------------------------
134
+ aneSettings | INFO | Encryption successful: True - gAAAAABox0DLjm7IOmFNRio8FYnp5tLVtMqPFpx5qFbbeot_jIUNah8XqLqHhPNmvaw1HpIBe0ebsna7ou8BrVnQ9erv6Fr1VK_O7PC3xDDXQXSEnyA2WhE=
135
+ aneSettings | INFO | Decryption successful: True - Sensitive Information
136
+ aneSettings | INFO | ------------------------------------------------------------------------------------------------------------------------------------------------------
137
+ aneSettings | INFO | Encode successful: True - U2Vuc2l0aXZlIEluZm9ybWF0aW9u
138
+ aneSettings | INFO | Decode successful: True - Sensitive Information
139
+ aneSettings | INFO | ------------------------------------------------------------------------------------------------------------------------------------------------------
140
+ aneSettings | INFO | New fn_key: RAl2XHZUwXQyZwXdzwVWJGKDDSwyJluh41De9KHw9oI=
141
+ aneSettings | INFO | : Use this key to replace the one in .env.settings `fn_key`
142
+ ```
143
+
144
+ ---
145
+
146
+ ### **_ProjectRoot:_**
147
+
148
+ ```python
149
+ # Basic Logging and Project Path Operations
150
+ from aneSettings import logger, project_root
151
+
152
+ # Constants
153
+ FORMAT_PADDING = 25
154
+ SEPARATOR_LINE = "-" * 150
155
+
156
+
157
+ def log_formatted(key, value):
158
+ """Helper to standardize log output."""
159
+ logger.info(f'{key:>{FORMAT_PADDING}}: {value}')
160
+
161
+
162
+ if __name__ == "__main__":
163
+ # -------------------------------------------------------------------------------------------------
164
+ # Example of Project Path Operations
165
+ # -------------------------------------------------------------------------------------------------
166
+
167
+ # Get project-related paths
168
+ root_path = project_root
169
+ config_path = project_root / "config"
170
+ data_path = project_root / "data"
171
+
172
+ logger.info(SEPARATOR_LINE)
173
+ log_formatted(key="Project root", value=root_path)
174
+ log_formatted(key="Config path", value=config_path)
175
+ log_formatted(key="Data path", value=data_path)
176
+ logger.info(SEPARATOR_LINE)
177
+ ```
178
+
179
+ #### **_output_**
180
+
181
+ ```shell
182
+ aneSettings | INFO | ------------------------------------------------------------------------------------------------------------------------------------------------------
183
+ aneSettings | INFO | Project root: {project_root}
184
+ aneSettings | INFO | Config path: {project_root}/config
185
+ aneSettings | INFO | Data path: {project_root}/data
186
+ aneSettings | INFO | ------------------------------------------------------------------------------------------------------------------------------------------------------
187
+ ```
@@ -0,0 +1,245 @@
1
+ import os
2
+ import re
3
+ import json
4
+ import yaml
5
+
6
+ from typing import Any
7
+ from string import Template
8
+ from pydantic.v1 import BaseSettings, Extra
9
+ from pydantic.v1.env_settings import SettingsSourceCallable
10
+ from .templates import ConfigSettings
11
+ from .ProjectRoot import project_root
12
+ from .__about__ import __version__
13
+
14
+ # Constants
15
+ ENV_FILE_SETTINGS = ".env.settings"
16
+ ENV_FILE_SECRETS = ".env.secrets"
17
+ DEFAULT_CONFIG_PATH = os.path.join(project_root(), "app")
18
+ DEFAULT_SECRET_PATH = os.path.join(DEFAULT_CONFIG_PATH, "secrets")
19
+ VALIDATION_RULES = {
20
+ "log_level": "ERROR",
21
+ "log_appname": "ERROR",
22
+ }
23
+
24
+ # Constant for an encryption key
25
+ FN_KEY = "Z46GMhKyW9rHU8c2T0296Zgr153HSIPD5mo3-faiHdQ="
26
+
27
+ CONFIG_KEY_PATTERN = re.compile(r"^[^#].+=[\"|\']*.*[\"|\']*$") # Regex to match key-value pairs
28
+
29
+
30
+ class ConfigManager:
31
+ """
32
+ Manages configuration settings, ensuring directories and files are set up properly while
33
+ validating and loading configuration data. Designed to handle and validate application
34
+ settings and secrets stored in files.
35
+
36
+ This class provides methods for loading configurations from files, managing directories,
37
+ and validating configurations based on predefined rules. It ensures that the required
38
+ directory structures and default configurations are initialized, with support for
39
+ overwriting these defaults by loading external configuration files.
40
+
41
+ :ivar parameters: Stores key-value pairs loaded from configuration files.
42
+ :type parameters: dict
43
+ :ivar validation_rules: Defines required configuration keys and their validation levels.
44
+ :type validation_rules: dict
45
+ :ivar errors: Tracks any errors or missing required keys during validation.
46
+ :type errors: dict
47
+ :ivar error_occurred: Indicates whether an error state exists in the configuration.
48
+ :type error_occurred: bool
49
+ :ivar config_dir: Path to the directory where configuration files are stored.
50
+ :type config_dir: str
51
+ :ivar secret_dir: Path to the directory where secret files are stored.
52
+ :type secret_dir: str
53
+ """
54
+
55
+ def __init__(self, validation_rules=None, config_path=DEFAULT_CONFIG_PATH, secret_path=DEFAULT_SECRET_PATH):
56
+ self.parameters: dict[str, str] = {}
57
+ self.validation_rules: dict[str, str] = validation_rules or {}
58
+ self.errors: dict[str, str] = {}
59
+ self.error_occurred: bool = False
60
+ self.config_dir: str = config_path
61
+ self.secret_dir: str = secret_path
62
+
63
+ self._setup_directories_and_configs()
64
+
65
+ def _setup_directories_and_configs(self):
66
+ """
67
+ Sets up the necessary directories and configuration files required for the application
68
+ to run. This includes ensuring that the directories exist, initializing necessary
69
+ configuration files with default values if they do not exist, and loading configurations
70
+ from these files.
71
+
72
+ Supported directories include the configuration directory and the secret directory.
73
+
74
+ The method also ensures environmental and secret configuration files exist by default
75
+ and reads configuration values from them.
76
+
77
+ :raises FileNotFoundError: If a necessary directory or file cannot be found.
78
+ """
79
+ self._ensure_directory(self.config_dir)
80
+ self._ensure_directory(self.secret_dir)
81
+ # self._initialize_file(os.path.join(self.config_dir, ENV_FILE_SETTINGS),
82
+ # Template(ConfigSettings.DEFAULT_ENV_SETTINGS).safe_substitute(fn_key=Fernet.generate_key().decode()))
83
+ self._initialize_file(os.path.join(self.config_dir, ENV_FILE_SETTINGS),
84
+ Template(ConfigSettings.DEFAULT_ENV_SETTINGS).safe_substitute(fn_key='l911qB1keWvIykhvswzdKCQbr6h35Cabu8OeckOUbP4='))
85
+ self.load_configuration(os.path.join(self.config_dir, ENV_FILE_SETTINGS))
86
+
87
+ secrets_file = os.path.join(self.secret_dir, ENV_FILE_SECRETS)
88
+ self._initialize_file(secrets_file, ConfigSettings.DEFAULT_ENV_SECRET_SETTINGS)
89
+
90
+ if os.path.exists(secrets_file):
91
+ self.load_configuration(secrets_file)
92
+
93
+ # keys_file = os.path.join(self.secret_dir, KEYS_FILE_SECRETS)
94
+ # self._initialize_file(keys_file, Keys.create_default_keys())
95
+
96
+ @staticmethod
97
+ def _ensure_directory(path: str):
98
+ """
99
+ Ensures that the specified directory exists. If the directory does not exist,
100
+ it will be created. If it already exists, no action will be taken.
101
+
102
+ :param path: The directory path to ensure exists.
103
+ :type path: str
104
+ :return: None
105
+ """
106
+ os.makedirs(path, exist_ok=True)
107
+
108
+ @staticmethod
109
+ def _initialize_file(filepath: str, default_content: str = ""):
110
+ """
111
+ Initializes a file at the specified filepath. If the file does not already exist,
112
+ it creates the file and optionally writes the provided default content to it.
113
+
114
+ :param filepath: The path to the file to be initialized.
115
+ :type filepath: str
116
+ :param default_content: The content to write to the file if it is created.
117
+ Defaults to an empty string.
118
+ :type default_content: str, optional
119
+ """
120
+ if not os.path.exists(filepath):
121
+ with open(filepath, "w") as file:
122
+ file.write(default_content)
123
+
124
+ def load_configuration(self, filename: str):
125
+ """
126
+ Loads configuration parameters from a specified file. The function reads the file line-by-line,
127
+ searching for configuration keys matching a predefined pattern. If a valid key-value pair is
128
+ found, it is parsed and added to the `parameters` dictionary of the instance. This allows dynamic
129
+ configuration loading from external files.
130
+
131
+ :param filename: The path to the configuration file to read.
132
+ :type filename: str
133
+ :return: None
134
+ """
135
+ with open(filename, "r") as file:
136
+ for line in file:
137
+ if CONFIG_KEY_PATTERN.search(line):
138
+ key, value = self._parse_line(line)
139
+ self.parameters[key] = value
140
+
141
+ @staticmethod
142
+ def _parse_line(line: str) -> tuple[str, str]:
143
+ """
144
+ Parses a configuration line, extracting a key-value pair by removing comments and cleaning whitespace and
145
+ quotes. The method ensures that the key is converted to uppercase for uniformity.
146
+
147
+ :param line: A string containing a configuration line, which may contain a key-value pair and comments.
148
+ :type line: str
149
+
150
+ :return: A tuple containing the extracted key (in uppercase) and cleaned value.
151
+ :rtype: tuple[str, str]
152
+ """
153
+ key_value = line.split("#", 1)[0].strip() # Remove comments
154
+ key, value = key_value.split("=", 1)
155
+ return key.strip().upper(), value.strip().strip('"\'') # Cleanup quotes
156
+
157
+ def validate_configuration(self):
158
+ """
159
+ Validates the configuration parameters against the defined validation rules and
160
+ determines whether any errors have occurred. Missing parameters are identified,
161
+ and detailed error messages are generated for each missing parameter according
162
+ to its required validation level.
163
+
164
+ :raises KeyError: if required, parameters are not uppercased in the validation rules.
165
+
166
+ :return: None
167
+ """
168
+ self.errors = {
169
+ param.upper(): json.dumps({
170
+ "settingRequired": {
171
+ "details": f"'{param}' is missing.",
172
+ "level": level,
173
+ }
174
+ })
175
+ for param, level in self.validation_rules.items()
176
+ if param.upper() not in self.parameters
177
+ }
178
+ self.error_occurred = any("ERROR" in error.upper() for error in self.errors.values())
179
+
180
+
181
+ class Settings(BaseSettings):
182
+ """
183
+ Manages application settings and configurations, enabling the retrieval and
184
+ management of environment variables and project-specific attributes. This
185
+ class serves as a centralized configuration handler to ensure consistent
186
+ and systematic access to application settings.
187
+ """
188
+ VERSION_CORE: str = __version__
189
+ FN_KEY: str = os.getenv("FN_KEY", FN_KEY)
190
+ PROJECT_ROOT: str = f'{project_root()}'
191
+
192
+ class Config:
193
+ extra = Extra.allow
194
+ env_prefix = "medium_"
195
+
196
+ @classmethod
197
+ def customise_sources(
198
+ cls,
199
+ init_settings: SettingsSourceCallable,
200
+ env_settings: SettingsSourceCallable,
201
+ file_secret_settings: SettingsSourceCallable,
202
+ ) -> tuple[SettingsSourceCallable, ...]:
203
+ """
204
+ Customizes and overrides the default sequence of settings sources for loading
205
+ configuration values. Takes three callable sources as input—`init_settings`,
206
+ `env_settings`, and `file_secret_settings`—and allows users to specify their
207
+ custom sequence of sources to be applied.
208
+
209
+ :param init_settings: Initial callable source for fetching configuration values.
210
+ :param env_settings: Callable source for fetching environment variable-based
211
+ configuration values.
212
+ :param file_secret_settings: Callable source for fetching configuration values
213
+ from secret files.
214
+ :return: A tuple of callable sources representing the customized sequence of
215
+ settings to be used.
216
+ """
217
+ return init_settings, env_settings, env_secrets_settings
218
+
219
+
220
+ def env_secrets_settings(_settings: BaseSettings) -> dict[str, Any]:
221
+ """
222
+ Processes the given BaseSettings object to extract environment-specific
223
+ secret settings and returns them as a dictionary.
224
+
225
+ This function uses a YAML-safe loader to transform configuration
226
+ parameters managed by the config_manager into a dictionary of secrets,
227
+ specifically designed for applications with environment-specific settings.
228
+
229
+ :param _settings: An instance of BaseSettings that provides application
230
+ configuration settings.
231
+ :type _settings: BaseSettings
232
+ :return: A dictionary containing the parsed environment-specific
233
+ secret settings.
234
+ :rtype: dict[str, Any]
235
+ """
236
+ return yaml.safe_load(str(config_manager.parameters))
237
+
238
+
239
+ # Initialize and validate configuration
240
+ config_manager = ConfigManager(VALIDATION_RULES)
241
+ config_manager.validate_configuration()
242
+
243
+ # Check for any unset settings
244
+ settings_not_set = config_manager.errors
245
+ settings = Settings()