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.
- anesettings-2025.9.0/LICENSE +21 -0
- anesettings-2025.9.0/PKG-INFO +202 -0
- anesettings-2025.9.0/ReadMe.md +187 -0
- anesettings-2025.9.0/aneSettings/ConfigSettings.py +245 -0
- anesettings-2025.9.0/aneSettings/CustomLogging.py +65 -0
- anesettings-2025.9.0/aneSettings/Encryption.py +158 -0
- anesettings-2025.9.0/aneSettings/ProjectRoot.py +81 -0
- anesettings-2025.9.0/aneSettings/__about__.py +5 -0
- anesettings-2025.9.0/aneSettings/__init__.py +27 -0
- anesettings-2025.9.0/aneSettings/templates/ConfigSettings.py +45 -0
- anesettings-2025.9.0/aneSettings/templates/__init__.py +0 -0
- anesettings-2025.9.0/aneSettings.egg-info/PKG-INFO +202 -0
- anesettings-2025.9.0/aneSettings.egg-info/SOURCES.txt +20 -0
- anesettings-2025.9.0/aneSettings.egg-info/dependency_links.txt +1 -0
- anesettings-2025.9.0/aneSettings.egg-info/requires.txt +4 -0
- anesettings-2025.9.0/aneSettings.egg-info/top_level.txt +1 -0
- anesettings-2025.9.0/pyproject.toml +30 -0
- anesettings-2025.9.0/setup.cfg +4 -0
- anesettings-2025.9.0/tests/test_ConfigSettings_ConfigManager.py +76 -0
- anesettings-2025.9.0/tests/test_ConfigSettings_Settings.py +40 -0
- anesettings-2025.9.0/tests/test_Encryption.py +114 -0
- anesettings-2025.9.0/tests/test_ProjectRoot.py +106 -0
|
@@ -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()
|