glean-config 0.1.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,312 @@
1
+ Metadata-Version: 2.3
2
+ Name: glean-config
3
+ Version: 0.1.0
4
+ Summary: Add your description here
5
+ Author: grimmdgg
6
+ Author-email: grimmdgg <grimmdgg@gmail.com>
7
+ Requires-Dist: tomli-w>=1.2.0
8
+ Requires-Python: >=3.14
9
+ Description-Content-Type: text/markdown
10
+
11
+ # glean-config
12
+
13
+ A flexible, thread-safe Python configuration manager using TOML files with support for environment variables, base64 encoding, and fileless operation modes.
14
+
15
+ ## Features
16
+
17
+ - **TOML-based configuration**: Easy-to-read and write configuration files
18
+ - **Singleton pattern**: Ensures consistent configuration across your application
19
+ - **Thread-safe**: Built-in locking for concurrent access
20
+ - **Environment variable integration**: Auto-load environment variables by name or regex pattern
21
+ - **Automatic base64 encoding**: Prefix keys with `encoded_` for automatic encoding/decoding
22
+ - **Fileless mode**: Run without a config file for testing or ephemeral configurations
23
+ - **Context manager support**: Use with `with` statement for automatic saving
24
+
25
+ ## Installation
26
+
27
+ ```bash
28
+ pip install glean-config
29
+ ```
30
+
31
+ Or for development:
32
+
33
+ ```bash
34
+ git clone <repository-url>
35
+ cd glean-config
36
+ pip install -e .
37
+ ```
38
+
39
+ ## Quick Start
40
+
41
+ ### Basic Usage
42
+
43
+ ```python
44
+ from glean_config.config import Glean_config
45
+
46
+ # Load or create a config file
47
+ config = Glean_config.get_instance('config.toml')
48
+
49
+ # Set values
50
+ config['api_key'] = 'your-api-key'
51
+ config['debug'] = 'true'
52
+
53
+ # Get values
54
+ api_key = config['api_key']
55
+
56
+ # Save changes
57
+ config.save()
58
+ ```
59
+
60
+ ### Using the CLI
61
+
62
+ ```bash
63
+ # Create/view config with a specific file
64
+ python -m glean_config -f config.toml
65
+
66
+ # Add a key-value pair
67
+ python -m glean_config -f config.toml -a "database_url::postgresql://localhost/mydb"
68
+
69
+ # Show a specific key
70
+ python -m glean_config -f config.toml -k database_url
71
+
72
+ # Use environment variable for config file location
73
+ export GLEAN_CONFIG_FILE=/path/to/config.toml
74
+ python -m glean_config -k database_url
75
+
76
+ # Run in fileless mode (no file or env var needed)
77
+ python -m glean_config -a "temp_key::temp_value"
78
+ ```
79
+
80
+ ## Configuration Modes
81
+
82
+ ### 1. File-based Mode (Default)
83
+
84
+ ```python
85
+ config = Glean_config.get_instance('myconfig.toml')
86
+ ```
87
+
88
+ Loads configuration from the specified TOML file. Creates the file if it doesn't exist.
89
+
90
+ ### 2. Environment Variable Mode
91
+
92
+ ```python
93
+ # Set the environment variable
94
+ import os
95
+ os.environ['GLEAN_CONFIG_FILE'] = '/path/to/config.toml'
96
+
97
+ # No file argument needed
98
+ config = Glean_config.get_instance()
99
+ ```
100
+
101
+ ### 3. Fileless Mode
102
+
103
+ ```python
104
+ # No file, no environment variable
105
+ config = Glean_config.get_instance()
106
+ # Config exists only in memory
107
+ config['key'] = 'value'
108
+ ```
109
+
110
+ ## Advanced Features
111
+
112
+ ### Automatic Base64 Encoding
113
+
114
+ Keys starting with `encoded_` are automatically base64 encoded when set and decoded when retrieved:
115
+
116
+ ```python
117
+ config['encoded_password'] = 'my-secret-password'
118
+ # Stored as base64 in the file
119
+
120
+ password = config['encoded_password']
121
+ # Returns: 'my-secret-password' (decoded)
122
+ ```
123
+
124
+ ### Environment Variable Integration
125
+
126
+ Configure your TOML file to automatically load environment variables:
127
+
128
+ ```toml
129
+ config_name = 'myapp'
130
+
131
+ [environment]
132
+ # Regex pattern to match environment variables
133
+ env_rex = '^MYAPP_'
134
+ # Explicit list of environment variables to load
135
+ env = ['DATABASE_URL', 'SECRET_KEY']
136
+
137
+ [config_items]
138
+ # Your config items here
139
+ ```
140
+
141
+ Now environment variables matching the pattern or in the list are automatically loaded:
142
+
143
+ ```python
144
+ import os
145
+ os.environ['MYAPP_DEBUG'] = 'true'
146
+ os.environ['DATABASE_URL'] = 'postgresql://localhost/db'
147
+
148
+ config = Glean_config.get_instance('config.toml')
149
+ print(config['MYAPP_DEBUG']) # 'true'
150
+ print(config['DATABASE_URL']) # 'postgresql://localhost/db'
151
+ ```
152
+
153
+ ### Context Manager
154
+
155
+ ```python
156
+ with Glean_config.get_instance('config.toml') as config:
157
+ config['key'] = 'value'
158
+ config['another_key'] = 'another_value'
159
+ # Automatically saved on exit
160
+ ```
161
+
162
+ ### Bulk Configuration
163
+
164
+ ```python
165
+ # Replace entire config
166
+ new_config = {
167
+ 'api_key': 'new-key',
168
+ 'timeout': '30',
169
+ 'retries': '3'
170
+ }
171
+ config.set_config(new_config)
172
+
173
+ # Merge with existing config
174
+ config.set_config(new_config, merge=True)
175
+ ```
176
+
177
+ ### Export Formats
178
+
179
+ ```python
180
+ # Export as TOML string
181
+ toml_str = config.get_toml()
182
+
183
+ # Export as JSON string
184
+ json_str = config.get_json()
185
+ ```
186
+
187
+ ## TOML File Structure
188
+
189
+ ```toml
190
+ config_name = 'my_application'
191
+
192
+ [environment]
193
+ # Regex pattern for environment variables to auto-load
194
+ env_rex = '^AP_'
195
+ # Explicit environment variables to load
196
+ env = ['DATABASE_URL', 'SECRET_KEY']
197
+
198
+ [config_items]
199
+ # Your application configuration
200
+ api_key = "your-api-key"
201
+ debug = "false"
202
+ timeout = "30"
203
+ encoded_password = "bXktc2VjcmV0LXBhc3N3b3Jk" # base64 encoded
204
+ ```
205
+
206
+ ## API Reference
207
+
208
+ ### `Glean_config.get_instance(toml_file=None)`
209
+
210
+ Get or create the singleton config instance.
211
+
212
+ - **toml_file** (str, optional): Path to TOML file. If `None`, uses `GLEAN_CONFIG_FILE` env var or fileless mode.
213
+ - **Returns**: `Glean_config` instance
214
+
215
+ ### Instance Methods
216
+
217
+ #### `config[key]` / `config[key] = value`
218
+
219
+ Get or set configuration values.
220
+
221
+ #### `save(force=False)`
222
+
223
+ Save configuration to file (no-op in fileless mode).
224
+
225
+ - **force** (bool): Save even if not modified
226
+
227
+ #### `get_config()`
228
+
229
+ Get the entire config_items dictionary.
230
+
231
+ - **Returns**: dict
232
+
233
+ #### `set_config(config, merge=False)`
234
+
235
+ Set the entire config_items dictionary.
236
+
237
+ - **config** (dict): New configuration
238
+ - **merge** (bool): Merge with existing config instead of replacing
239
+
240
+ #### `get_toml()`
241
+
242
+ Export configuration as TOML string.
243
+
244
+ - **Returns**: str
245
+
246
+ #### `get_json()`
247
+
248
+ Export configuration as JSON string.
249
+
250
+ - **Returns**: str
251
+
252
+ #### `parameters()`
253
+
254
+ Get list of top-level configuration keys.
255
+
256
+ - **Returns**: list[str]
257
+
258
+ #### `close()`
259
+
260
+ Explicitly save and close the configuration.
261
+
262
+ ## Testing
263
+
264
+ ```bash
265
+ # Run all tests
266
+ pytest tests/
267
+
268
+ # Run with coverage
269
+ pytest tests/ --cov=glean_config
270
+
271
+ # Run specific test
272
+ pytest tests/test_config.py::TestGleanConfig::test_fileless_mode_no_args
273
+ ```
274
+
275
+ ## Thread Safety
276
+
277
+ All operations are thread-safe using internal locks:
278
+
279
+ - Object-level lock for singleton instance creation and config updates
280
+ - File-level lock for concurrent file I/O operations
281
+
282
+ ## Error Handling
283
+
284
+ ### `ConfigObjectError`
285
+
286
+ Raised when trying to instantiate `Glean_config` directly instead of using `get_instance()`.
287
+
288
+ ```python
289
+ from glean_config.config import Glean_config, ConfigObjectError
290
+
291
+ try:
292
+ config = Glean_config('config.toml') # Wrong!
293
+ except ConfigObjectError:
294
+ config = Glean_config.get_instance('config.toml') # Correct
295
+ ```
296
+
297
+ ### `KeyError`
298
+
299
+ Raised when configuration structure is invalid (missing `config_items` key).
300
+
301
+ ## License
302
+
303
+ MIT
304
+
305
+ ## Contributing
306
+
307
+ Contributions are welcome! Please ensure:
308
+
309
+ 1. All tests pass: `pytest tests/`
310
+ 2. Code follows PEP 8 style guidelines
311
+ 3. New features include tests
312
+ 4. Documentation is updated
@@ -0,0 +1,302 @@
1
+ # glean-config
2
+
3
+ A flexible, thread-safe Python configuration manager using TOML files with support for environment variables, base64 encoding, and fileless operation modes.
4
+
5
+ ## Features
6
+
7
+ - **TOML-based configuration**: Easy-to-read and write configuration files
8
+ - **Singleton pattern**: Ensures consistent configuration across your application
9
+ - **Thread-safe**: Built-in locking for concurrent access
10
+ - **Environment variable integration**: Auto-load environment variables by name or regex pattern
11
+ - **Automatic base64 encoding**: Prefix keys with `encoded_` for automatic encoding/decoding
12
+ - **Fileless mode**: Run without a config file for testing or ephemeral configurations
13
+ - **Context manager support**: Use with `with` statement for automatic saving
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ pip install glean-config
19
+ ```
20
+
21
+ Or for development:
22
+
23
+ ```bash
24
+ git clone <repository-url>
25
+ cd glean-config
26
+ pip install -e .
27
+ ```
28
+
29
+ ## Quick Start
30
+
31
+ ### Basic Usage
32
+
33
+ ```python
34
+ from glean_config.config import Glean_config
35
+
36
+ # Load or create a config file
37
+ config = Glean_config.get_instance('config.toml')
38
+
39
+ # Set values
40
+ config['api_key'] = 'your-api-key'
41
+ config['debug'] = 'true'
42
+
43
+ # Get values
44
+ api_key = config['api_key']
45
+
46
+ # Save changes
47
+ config.save()
48
+ ```
49
+
50
+ ### Using the CLI
51
+
52
+ ```bash
53
+ # Create/view config with a specific file
54
+ python -m glean_config -f config.toml
55
+
56
+ # Add a key-value pair
57
+ python -m glean_config -f config.toml -a "database_url::postgresql://localhost/mydb"
58
+
59
+ # Show a specific key
60
+ python -m glean_config -f config.toml -k database_url
61
+
62
+ # Use environment variable for config file location
63
+ export GLEAN_CONFIG_FILE=/path/to/config.toml
64
+ python -m glean_config -k database_url
65
+
66
+ # Run in fileless mode (no file or env var needed)
67
+ python -m glean_config -a "temp_key::temp_value"
68
+ ```
69
+
70
+ ## Configuration Modes
71
+
72
+ ### 1. File-based Mode (Default)
73
+
74
+ ```python
75
+ config = Glean_config.get_instance('myconfig.toml')
76
+ ```
77
+
78
+ Loads configuration from the specified TOML file. Creates the file if it doesn't exist.
79
+
80
+ ### 2. Environment Variable Mode
81
+
82
+ ```python
83
+ # Set the environment variable
84
+ import os
85
+ os.environ['GLEAN_CONFIG_FILE'] = '/path/to/config.toml'
86
+
87
+ # No file argument needed
88
+ config = Glean_config.get_instance()
89
+ ```
90
+
91
+ ### 3. Fileless Mode
92
+
93
+ ```python
94
+ # No file, no environment variable
95
+ config = Glean_config.get_instance()
96
+ # Config exists only in memory
97
+ config['key'] = 'value'
98
+ ```
99
+
100
+ ## Advanced Features
101
+
102
+ ### Automatic Base64 Encoding
103
+
104
+ Keys starting with `encoded_` are automatically base64 encoded when set and decoded when retrieved:
105
+
106
+ ```python
107
+ config['encoded_password'] = 'my-secret-password'
108
+ # Stored as base64 in the file
109
+
110
+ password = config['encoded_password']
111
+ # Returns: 'my-secret-password' (decoded)
112
+ ```
113
+
114
+ ### Environment Variable Integration
115
+
116
+ Configure your TOML file to automatically load environment variables:
117
+
118
+ ```toml
119
+ config_name = 'myapp'
120
+
121
+ [environment]
122
+ # Regex pattern to match environment variables
123
+ env_rex = '^MYAPP_'
124
+ # Explicit list of environment variables to load
125
+ env = ['DATABASE_URL', 'SECRET_KEY']
126
+
127
+ [config_items]
128
+ # Your config items here
129
+ ```
130
+
131
+ Now environment variables matching the pattern or in the list are automatically loaded:
132
+
133
+ ```python
134
+ import os
135
+ os.environ['MYAPP_DEBUG'] = 'true'
136
+ os.environ['DATABASE_URL'] = 'postgresql://localhost/db'
137
+
138
+ config = Glean_config.get_instance('config.toml')
139
+ print(config['MYAPP_DEBUG']) # 'true'
140
+ print(config['DATABASE_URL']) # 'postgresql://localhost/db'
141
+ ```
142
+
143
+ ### Context Manager
144
+
145
+ ```python
146
+ with Glean_config.get_instance('config.toml') as config:
147
+ config['key'] = 'value'
148
+ config['another_key'] = 'another_value'
149
+ # Automatically saved on exit
150
+ ```
151
+
152
+ ### Bulk Configuration
153
+
154
+ ```python
155
+ # Replace entire config
156
+ new_config = {
157
+ 'api_key': 'new-key',
158
+ 'timeout': '30',
159
+ 'retries': '3'
160
+ }
161
+ config.set_config(new_config)
162
+
163
+ # Merge with existing config
164
+ config.set_config(new_config, merge=True)
165
+ ```
166
+
167
+ ### Export Formats
168
+
169
+ ```python
170
+ # Export as TOML string
171
+ toml_str = config.get_toml()
172
+
173
+ # Export as JSON string
174
+ json_str = config.get_json()
175
+ ```
176
+
177
+ ## TOML File Structure
178
+
179
+ ```toml
180
+ config_name = 'my_application'
181
+
182
+ [environment]
183
+ # Regex pattern for environment variables to auto-load
184
+ env_rex = '^AP_'
185
+ # Explicit environment variables to load
186
+ env = ['DATABASE_URL', 'SECRET_KEY']
187
+
188
+ [config_items]
189
+ # Your application configuration
190
+ api_key = "your-api-key"
191
+ debug = "false"
192
+ timeout = "30"
193
+ encoded_password = "bXktc2VjcmV0LXBhc3N3b3Jk" # base64 encoded
194
+ ```
195
+
196
+ ## API Reference
197
+
198
+ ### `Glean_config.get_instance(toml_file=None)`
199
+
200
+ Get or create the singleton config instance.
201
+
202
+ - **toml_file** (str, optional): Path to TOML file. If `None`, uses `GLEAN_CONFIG_FILE` env var or fileless mode.
203
+ - **Returns**: `Glean_config` instance
204
+
205
+ ### Instance Methods
206
+
207
+ #### `config[key]` / `config[key] = value`
208
+
209
+ Get or set configuration values.
210
+
211
+ #### `save(force=False)`
212
+
213
+ Save configuration to file (no-op in fileless mode).
214
+
215
+ - **force** (bool): Save even if not modified
216
+
217
+ #### `get_config()`
218
+
219
+ Get the entire config_items dictionary.
220
+
221
+ - **Returns**: dict
222
+
223
+ #### `set_config(config, merge=False)`
224
+
225
+ Set the entire config_items dictionary.
226
+
227
+ - **config** (dict): New configuration
228
+ - **merge** (bool): Merge with existing config instead of replacing
229
+
230
+ #### `get_toml()`
231
+
232
+ Export configuration as TOML string.
233
+
234
+ - **Returns**: str
235
+
236
+ #### `get_json()`
237
+
238
+ Export configuration as JSON string.
239
+
240
+ - **Returns**: str
241
+
242
+ #### `parameters()`
243
+
244
+ Get list of top-level configuration keys.
245
+
246
+ - **Returns**: list[str]
247
+
248
+ #### `close()`
249
+
250
+ Explicitly save and close the configuration.
251
+
252
+ ## Testing
253
+
254
+ ```bash
255
+ # Run all tests
256
+ pytest tests/
257
+
258
+ # Run with coverage
259
+ pytest tests/ --cov=glean_config
260
+
261
+ # Run specific test
262
+ pytest tests/test_config.py::TestGleanConfig::test_fileless_mode_no_args
263
+ ```
264
+
265
+ ## Thread Safety
266
+
267
+ All operations are thread-safe using internal locks:
268
+
269
+ - Object-level lock for singleton instance creation and config updates
270
+ - File-level lock for concurrent file I/O operations
271
+
272
+ ## Error Handling
273
+
274
+ ### `ConfigObjectError`
275
+
276
+ Raised when trying to instantiate `Glean_config` directly instead of using `get_instance()`.
277
+
278
+ ```python
279
+ from glean_config.config import Glean_config, ConfigObjectError
280
+
281
+ try:
282
+ config = Glean_config('config.toml') # Wrong!
283
+ except ConfigObjectError:
284
+ config = Glean_config.get_instance('config.toml') # Correct
285
+ ```
286
+
287
+ ### `KeyError`
288
+
289
+ Raised when configuration structure is invalid (missing `config_items` key).
290
+
291
+ ## License
292
+
293
+ MIT
294
+
295
+ ## Contributing
296
+
297
+ Contributions are welcome! Please ensure:
298
+
299
+ 1. All tests pass: `pytest tests/`
300
+ 2. Code follows PEP 8 style guidelines
301
+ 3. New features include tests
302
+ 4. Documentation is updated
@@ -0,0 +1,22 @@
1
+ [project]
2
+ name = "glean-config"
3
+ version = "0.1.0"
4
+ description = "Add your description here"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "grimmdgg", email = "grimmdgg@gmail.com" }
8
+ ]
9
+ requires-python = ">=3.14"
10
+ dependencies = [
11
+ "tomli-w>=1.2.0",
12
+ ]
13
+
14
+ [build-system]
15
+ requires = ["uv_build>=0.9.0,<0.10.0"]
16
+ build-backend = "uv_build"
17
+
18
+ [dependency-groups]
19
+ dev = [
20
+ "pytest>=8.4.2",
21
+ "pytest-cov>=7.0.0",
22
+ ]
@@ -0,0 +1 @@
1
+ # __init__.py
@@ -0,0 +1,70 @@
1
+ import argparse
2
+ from sys import exit
3
+
4
+ from glean_config.config import Glean_config
5
+
6
+ def main():
7
+ parser = argparse.ArgumentParser("Glean_config")
8
+ parser.add_argument(
9
+ '-f',
10
+ '--file',
11
+ dest='toml_file',
12
+ help='path to toml config file (optional: uses GLEAN_CONFIG_FILE env var or fileless mode if not provided)',
13
+ default=None
14
+ )
15
+ parser.add_argument(
16
+ '-a',
17
+ '--add',
18
+ dest='add_kvp',
19
+ help='add a kvp to file in the form "key::value"'
20
+ )
21
+
22
+ parser.add_argument(
23
+ '-k',
24
+ '--show_key',
25
+ dest='key',
26
+ help='display key value'
27
+ )
28
+
29
+ args = parser.parse_args()
30
+
31
+ # Get config instance (may use file, env var, or fileless mode)
32
+ config = Glean_config.get_instance(args.toml_file)
33
+
34
+
35
+
36
+ # Validate and process the add_kvp argument
37
+ try:
38
+ validate_parameters(args, config)
39
+ except ValueError as ve:
40
+ print(ve)
41
+ exit(1)
42
+
43
+ # Removed redundant call to config.get_toml()
44
+
45
+ def validate_parameters(args, config):
46
+ if args.add_kvp:
47
+
48
+ if "::" not in args.add_kvp:
49
+
50
+ raise ValueError("Error: The key-value pair must be in the form 'key::value'.")
51
+ key, value = args.add_kvp.split('::', 1)
52
+ config[key] = value
53
+
54
+
55
+ # Validate and process the key argument
56
+ if args.key:
57
+ if args.key not in config.get_config():
58
+ raise ValueError(f"Error: The key '{args.key}' does not exist in the configuration.")
59
+
60
+ print(f"{args.key}: {config[args.key]}")
61
+ exit(0)
62
+
63
+
64
+ config.save(force=True)
65
+ print(config.get_toml())
66
+
67
+
68
+ if __name__=="__main__":
69
+ main()
70
+
@@ -0,0 +1,161 @@
1
+ import tomllib
2
+ import json
3
+ import re
4
+ from base64 import b64decode, b64encode
5
+ from os import environ
6
+ from pathlib import Path, PurePath
7
+ from threading import Lock
8
+ import tomli_w
9
+ from typing import Any, TypeVar, TypeAlias
10
+
11
+ # Method-level generic for config value types.
12
+ # Use `Config[T]` in method signatures when you want a specific value type.
13
+ T = TypeVar('T')
14
+ # modern TypeAlias (Python 3.10+)
15
+ Config: TypeAlias = dict[str, T]
16
+
17
+
18
+ class ConfigObjectError(Exception):
19
+ def __init__(self):
20
+ super(ConfigObjectError,self).__init__("Please instantiate this class with the 'get_instance(toml_file:str)' class method")
21
+
22
+ class Glean_config():
23
+
24
+ _config_object: 'Glean_config | None' = None
25
+ _object_lock = Lock()
26
+ _file_lock = Lock()
27
+ # internal storage can hold heterogeneous values loaded from TOML
28
+ config: Config[Any] = {}
29
+
30
+ def __init__(self, toml_file: str | None = None, override: bool = False):
31
+ self.modified = True
32
+ self.fileless_mode = False
33
+
34
+ # Determine the file path
35
+ if toml_file is None:
36
+ # Check environment variable
37
+ toml_file = environ.get('glean_config_file')
38
+ if toml_file is None:
39
+ # Fileless mode
40
+ self.fileless_mode = True
41
+ self.toml_file = None
42
+ else:
43
+ self.toml_file = toml_file
44
+ else:
45
+ self.toml_file = toml_file
46
+
47
+ if not override:
48
+ raise ConfigObjectError()
49
+ else:
50
+ # Prepare config text
51
+ if self.fileless_mode:
52
+ config_text = Glean_config.empty_config_toml % 'fileless_config'
53
+ else:
54
+ config_text = Glean_config.empty_config_toml % PurePath(self.toml_file).name
55
+ if Path(self.toml_file).exists():
56
+ config_text = Path(self.toml_file).read_text(encoding='utf-8')
57
+ self.modified = False
58
+
59
+ self.config = tomllib.loads(config_text)
60
+ self._read_env()
61
+
62
+ def _read_env(self):
63
+ environment = self.config.get('environment', {})
64
+
65
+ env_vars = environment.get('env', [])
66
+ for var in env_vars:
67
+ value = environ.get(var)
68
+ if value is not None:
69
+ self.__setitem__(var, value)
70
+
71
+ env_rex = environment.get('env_rex')
72
+ if env_rex:
73
+ config_items = self.config.get('config_items', {})
74
+ for var in environ:
75
+ if var not in config_items and re.match(env_rex, var):
76
+ # environ is being iterated over its keys, so indexing is safe
77
+ self.__setitem__(var, environ[var])
78
+
79
+ def parameters(self) -> list[str]:
80
+ return list(self.config.keys())
81
+
82
+ def save(self, force: bool = False) -> None:
83
+ if self.fileless_mode:
84
+ # In fileless mode, just mark as not modified but don't save
85
+ self.modified = False
86
+ return
87
+
88
+ if (self.modified or force) and self.config and self.toml_file:
89
+ with Glean_config._file_lock:
90
+ with open(self.toml_file, mode="wb") as fp:
91
+ tomli_w.dump(self.config, fp)
92
+ self.modified = False
93
+
94
+ def get_toml(self)->str:
95
+ return tomli_w.dumps(self.config)
96
+
97
+ def get_json(self)->str:
98
+ return json.dumps(self.config)
99
+
100
+ @classmethod
101
+ def get_instance(cls, toml_file: str | None = None) -> 'Glean_config':
102
+ with cls._object_lock:
103
+ if not Glean_config._config_object:
104
+ Glean_config._config_object = Glean_config(toml_file=toml_file, override=True)
105
+ return Glean_config._config_object
106
+
107
+ def get_config(self) -> Config[Any]:
108
+ if 'config_items' not in self.config:
109
+ raise KeyError("Configuration structure invalid: 'config_items' key not found")
110
+ return self.config['config_items']
111
+
112
+ def set_config(self, config: Config[T], merge:bool = False) -> None:
113
+ with Glean_config._object_lock:
114
+ if 'config_items' not in self.config:
115
+ raise KeyError("Configuration structure invalid: 'config_items' key not found")
116
+ if merge:
117
+ self.config['config_items'].update(config)
118
+ else:
119
+ self.config['config_items'] = config
120
+ self.modified = True
121
+ self.save()
122
+
123
+ def decode(self,encoded:str)-> str:
124
+ return str(b64decode(encoded),'utf-8')
125
+
126
+ def __getitem__(self, key: str):
127
+ if 'config_items' not in self.config:
128
+ raise KeyError("Configuration structure invalid: 'config_items' key not found")
129
+ returnValue = self.config['config_items'].get(key, None)
130
+
131
+ if returnValue and key.startswith('encoded_'):
132
+ returnValue = self.decode(returnValue)
133
+ return returnValue
134
+
135
+ def __setitem__(self, key: str, value: str) -> None:
136
+ if 'config_items' not in self.config:
137
+ raise KeyError("Configuration structure invalid: 'config_items' key not found")
138
+ if key.startswith('encoded_'):
139
+ value = str(b64encode(bytes(value, 'utf-8')), 'utf-8')
140
+ self.config['config_items'][key] = value
141
+ self.modified = True
142
+
143
+ def __enter__(self) -> 'Glean_config':
144
+ return self
145
+
146
+ def __exit__(self, exc_type: type | None, exc_value: BaseException | None, traceback: object | None) -> None:
147
+ self.save()
148
+
149
+ def close(self) -> None:
150
+ """Explicitly save and close the config. Preferred over relying on __del__."""
151
+ self.save()
152
+
153
+
154
+ empty_config_toml=\
155
+ """
156
+ config_name = '%s'
157
+ [environment]
158
+ env_rex='^glean_'
159
+ env=[]
160
+ [config_items]
161
+ """
File without changes