config2py 0.1.41__tar.gz → 0.1.43__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.
- {config2py-0.1.41/config2py.egg-info → config2py-0.1.43}/PKG-INFO +85 -9
- config2py-0.1.41/PKG-INFO → config2py-0.1.43/README.md +79 -17
- {config2py-0.1.41 → config2py-0.1.43}/config2py/__init__.py +3 -0
- {config2py-0.1.41 → config2py-0.1.43}/config2py/base.py +6 -8
- {config2py-0.1.41 → config2py-0.1.43}/config2py/s_configparser.py +6 -5
- config2py-0.1.43/config2py/sync_store.py +380 -0
- {config2py-0.1.41 → config2py-0.1.43}/config2py/tests/__init__.py +2 -0
- config2py-0.1.43/config2py/tests/test_sync_store.py +316 -0
- {config2py-0.1.41 → config2py-0.1.43}/config2py/tests/utils_for_testing.py +2 -0
- {config2py-0.1.41 → config2py-0.1.43}/config2py/tools.py +1 -1
- {config2py-0.1.41 → config2py-0.1.43}/config2py/util.py +45 -23
- config2py-0.1.41/README.md → config2py-0.1.43/config2py.egg-info/PKG-INFO +93 -7
- {config2py-0.1.41 → config2py-0.1.43}/config2py.egg-info/SOURCES.txt +2 -0
- {config2py-0.1.41 → config2py-0.1.43}/setup.cfg +1 -1
- {config2py-0.1.41 → config2py-0.1.43}/LICENSE +0 -0
- {config2py-0.1.41 → config2py-0.1.43}/config2py/errors.py +0 -0
- {config2py-0.1.41 → config2py-0.1.43}/config2py/scrap/__init__.py +0 -0
- {config2py-0.1.41 → config2py-0.1.43}/config2py/tests/test_tools.py +0 -0
- {config2py-0.1.41 → config2py-0.1.43}/config2py.egg-info/dependency_links.txt +0 -0
- {config2py-0.1.41 → config2py-0.1.43}/config2py.egg-info/not-zip-safe +0 -0
- {config2py-0.1.41 → config2py-0.1.43}/config2py.egg-info/requires.txt +0 -0
- {config2py-0.1.41 → config2py-0.1.43}/config2py.egg-info/top_level.txt +0 -0
- {config2py-0.1.41 → config2py-0.1.43}/setup.py +0 -0
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: config2py
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.43
|
|
4
4
|
Summary: Simplified reading and writing configurations from various sources and formats
|
|
5
5
|
Home-page: https://github.com/i2mint/config2py
|
|
6
6
|
License: apache-2.0
|
|
7
7
|
Platform: any
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
9
|
License-File: LICENSE
|
|
10
|
+
Requires-Dist: dol
|
|
11
|
+
Requires-Dist: i2
|
|
12
|
+
Requires-Dist: importlib_resources; python_version < "3.9"
|
|
13
|
+
Dynamic: license-file
|
|
10
14
|
|
|
11
15
|
# config2py
|
|
12
16
|
|
|
@@ -107,7 +111,7 @@ In fact, `simple_config_getter` is a function to make configuration getters that
|
|
|
107
111
|
|
|
108
112
|
<img width="341" alt="image" src="https://github.com/i2mint/config2py/assets/1906276/09f287a8-05f9-4590-8664-10feda9ad617">
|
|
109
113
|
|
|
110
|
-
But where you can control what the central store (by default
|
|
114
|
+
But where you can control what the central store (by default a local configuration files store) is, and whether to first search in environment variables or not, and whether to ask the user for the value, if not found before, or not.
|
|
111
115
|
|
|
112
116
|
```python
|
|
113
117
|
from config2py import simple_config_getter, get_configs_local_store
|
|
@@ -127,7 +131,7 @@ print(*str(Sig(simple_config_getter)).split(','), sep='\n')
|
|
|
127
131
|
`ask_user_if_key_not_found` specifies whether to ask the user if a configuration key is not found. The default is `None`, which will result in checking if you're running in an interactive environment or not.
|
|
128
132
|
When you use `config2py` in production though, you should definitely specify `ask_user_if_key_not_found=False` to make that choice explicit.
|
|
129
133
|
|
|
130
|
-
The `configs_src` default is automatically set to be the `config2py/configs` folder of your
|
|
134
|
+
The `configs_src` default is automatically set to be the `config2py/configs` folder of your system's config directory (following XDG standards on Unix/Linux/macOS). You can override this with environment variables like `CONFIG2PY_CONFIG_DIR`, `CONFIG2PY_DATA_DIR`, etc., or the standard XDG variables.
|
|
131
135
|
|
|
132
136
|
Your central store will be `config_store_factory(configs_src)`, and since you can also specify `config_store_factory`, you have total control over the store.
|
|
133
137
|
|
|
@@ -207,12 +211,88 @@ It will return the value that the user entered last time, without prompting the
|
|
|
207
211
|
user again.
|
|
208
212
|
|
|
209
213
|
|
|
214
|
+
## SyncStore: Auto-Syncing Key-Value Stores
|
|
215
|
+
|
|
216
|
+
### Overview
|
|
217
|
+
|
|
218
|
+
`SyncStore` provides MutableMapping interfaces that automatically persist changes to backing storage. Changes sync immediately by default, or can be deferred using a context manager for efficient batch operations.
|
|
219
|
+
|
|
220
|
+
### Basic Usage
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
from config2py.sync_store import FileStore, JsonStore
|
|
224
|
+
|
|
225
|
+
# Auto-detected from .json extension
|
|
226
|
+
config = FileStore('config.json')
|
|
227
|
+
config['api_key'] = 'secret' # Syncs immediately
|
|
228
|
+
|
|
229
|
+
# Batch operations (deferred sync)
|
|
230
|
+
with config:
|
|
231
|
+
config['a'] = 1
|
|
232
|
+
config['b'] = 2
|
|
233
|
+
config['c'] = 3
|
|
234
|
+
# Syncs once on exit
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Nested Sections
|
|
238
|
+
|
|
239
|
+
```python
|
|
240
|
+
# Work with specific section via key_path
|
|
241
|
+
db_config = FileStore('config.json', key_path='database')
|
|
242
|
+
db_config['host'] = 'localhost' # Only affects database section
|
|
243
|
+
|
|
244
|
+
# Dotted notation for deep nesting
|
|
245
|
+
items = FileStore('config.json', key_path='app.settings.items')
|
|
246
|
+
items['item1'] = 'value'
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Supported Formats
|
|
250
|
+
|
|
251
|
+
Auto-detected by extension:
|
|
252
|
+
- `.json` - JSON (stdlib)
|
|
253
|
+
- `.ini`, `.cfg` - INI files (stdlib)
|
|
254
|
+
- `.yaml`, `.yml` - YAML (if PyYAML installed)
|
|
255
|
+
- `.toml` - TOML (if tomli/tomllib installed)
|
|
256
|
+
|
|
257
|
+
Register custom formats:
|
|
258
|
+
```python
|
|
259
|
+
from sync_store import register_extension
|
|
260
|
+
|
|
261
|
+
register_extension('.custom', my_loader, my_dumper)
|
|
262
|
+
store = FileStore('data.custom')
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Custom Backing Storage
|
|
266
|
+
|
|
267
|
+
```python
|
|
268
|
+
from config2py.sync_store import SyncStore
|
|
269
|
+
|
|
270
|
+
# Any backing storage via loader/dumper
|
|
271
|
+
def my_loader():
|
|
272
|
+
return fetch_from_database()
|
|
273
|
+
|
|
274
|
+
def my_dumper(data):
|
|
275
|
+
save_to_database(data)
|
|
276
|
+
|
|
277
|
+
store = SyncStore(my_loader, my_dumper)
|
|
278
|
+
store['key'] = 'value' # Calls my_dumper
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Key Classes
|
|
282
|
+
|
|
283
|
+
- **`SyncStore`** - Base class with loader/dumper functions
|
|
284
|
+
- **`FileStore`** - File-based with extension detection and key_path
|
|
285
|
+
- **`JsonStore`** - Explicit JSON with sensible defaults
|
|
286
|
+
|
|
287
|
+
|
|
210
288
|
# A few notable tools you can import from config2py
|
|
211
289
|
|
|
212
290
|
* `get_config`: Get a config value from a list of sources. See more below.
|
|
213
291
|
* `user_gettable`: Create a ``GettableContainer`` that asks the user for a value, optionally saving it.
|
|
214
292
|
* `ask_user_for_input`: Ask the user for input, optionally masking, validating and transforming the input.
|
|
215
|
-
* `
|
|
293
|
+
* `get_app_folder`: Returns the full path of a directory suitable for storing application-specific data for a given app name and folder kind (config, data, cache, state, runtime).
|
|
294
|
+
* `get_app_config_folder`: Specialized version of `get_app_folder` for configuration files.
|
|
295
|
+
* `get_app_data_folder`: Specialized version of `get_app_folder` for application data.
|
|
216
296
|
* `get_configs_local_store`: Get a local store (mapping interface of local files) of configs for a given app or package name
|
|
217
297
|
* `configs`: A default store instance for configs, defaulting to a local store under a default configuration local directory.
|
|
218
298
|
|
|
@@ -335,7 +415,3 @@ s['SOME_KEY']
|
|
|
335
415
|
|
|
336
416
|
More on that another day...
|
|
337
417
|
|
|
338
|
-
|
|
339
|
-
```python
|
|
340
|
-
|
|
341
|
-
```
|
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: config2py
|
|
3
|
-
Version: 0.1.41
|
|
4
|
-
Summary: Simplified reading and writing configurations from various sources and formats
|
|
5
|
-
Home-page: https://github.com/i2mint/config2py
|
|
6
|
-
License: apache-2.0
|
|
7
|
-
Platform: any
|
|
8
|
-
Description-Content-Type: text/markdown
|
|
9
|
-
License-File: LICENSE
|
|
10
|
-
|
|
11
1
|
# config2py
|
|
12
2
|
|
|
13
3
|
Simplified reading and writing configurations from various sources and formats.
|
|
@@ -107,7 +97,7 @@ In fact, `simple_config_getter` is a function to make configuration getters that
|
|
|
107
97
|
|
|
108
98
|
<img width="341" alt="image" src="https://github.com/i2mint/config2py/assets/1906276/09f287a8-05f9-4590-8664-10feda9ad617">
|
|
109
99
|
|
|
110
|
-
But where you can control what the central store (by default
|
|
100
|
+
But where you can control what the central store (by default a local configuration files store) is, and whether to first search in environment variables or not, and whether to ask the user for the value, if not found before, or not.
|
|
111
101
|
|
|
112
102
|
```python
|
|
113
103
|
from config2py import simple_config_getter, get_configs_local_store
|
|
@@ -127,7 +117,7 @@ print(*str(Sig(simple_config_getter)).split(','), sep='\n')
|
|
|
127
117
|
`ask_user_if_key_not_found` specifies whether to ask the user if a configuration key is not found. The default is `None`, which will result in checking if you're running in an interactive environment or not.
|
|
128
118
|
When you use `config2py` in production though, you should definitely specify `ask_user_if_key_not_found=False` to make that choice explicit.
|
|
129
119
|
|
|
130
|
-
The `configs_src` default is automatically set to be the `config2py/configs` folder of your
|
|
120
|
+
The `configs_src` default is automatically set to be the `config2py/configs` folder of your system's config directory (following XDG standards on Unix/Linux/macOS). You can override this with environment variables like `CONFIG2PY_CONFIG_DIR`, `CONFIG2PY_DATA_DIR`, etc., or the standard XDG variables.
|
|
131
121
|
|
|
132
122
|
Your central store will be `config_store_factory(configs_src)`, and since you can also specify `config_store_factory`, you have total control over the store.
|
|
133
123
|
|
|
@@ -207,12 +197,88 @@ It will return the value that the user entered last time, without prompting the
|
|
|
207
197
|
user again.
|
|
208
198
|
|
|
209
199
|
|
|
200
|
+
## SyncStore: Auto-Syncing Key-Value Stores
|
|
201
|
+
|
|
202
|
+
### Overview
|
|
203
|
+
|
|
204
|
+
`SyncStore` provides MutableMapping interfaces that automatically persist changes to backing storage. Changes sync immediately by default, or can be deferred using a context manager for efficient batch operations.
|
|
205
|
+
|
|
206
|
+
### Basic Usage
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
from config2py.sync_store import FileStore, JsonStore
|
|
210
|
+
|
|
211
|
+
# Auto-detected from .json extension
|
|
212
|
+
config = FileStore('config.json')
|
|
213
|
+
config['api_key'] = 'secret' # Syncs immediately
|
|
214
|
+
|
|
215
|
+
# Batch operations (deferred sync)
|
|
216
|
+
with config:
|
|
217
|
+
config['a'] = 1
|
|
218
|
+
config['b'] = 2
|
|
219
|
+
config['c'] = 3
|
|
220
|
+
# Syncs once on exit
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Nested Sections
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
# Work with specific section via key_path
|
|
227
|
+
db_config = FileStore('config.json', key_path='database')
|
|
228
|
+
db_config['host'] = 'localhost' # Only affects database section
|
|
229
|
+
|
|
230
|
+
# Dotted notation for deep nesting
|
|
231
|
+
items = FileStore('config.json', key_path='app.settings.items')
|
|
232
|
+
items['item1'] = 'value'
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Supported Formats
|
|
236
|
+
|
|
237
|
+
Auto-detected by extension:
|
|
238
|
+
- `.json` - JSON (stdlib)
|
|
239
|
+
- `.ini`, `.cfg` - INI files (stdlib)
|
|
240
|
+
- `.yaml`, `.yml` - YAML (if PyYAML installed)
|
|
241
|
+
- `.toml` - TOML (if tomli/tomllib installed)
|
|
242
|
+
|
|
243
|
+
Register custom formats:
|
|
244
|
+
```python
|
|
245
|
+
from sync_store import register_extension
|
|
246
|
+
|
|
247
|
+
register_extension('.custom', my_loader, my_dumper)
|
|
248
|
+
store = FileStore('data.custom')
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Custom Backing Storage
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
from config2py.sync_store import SyncStore
|
|
255
|
+
|
|
256
|
+
# Any backing storage via loader/dumper
|
|
257
|
+
def my_loader():
|
|
258
|
+
return fetch_from_database()
|
|
259
|
+
|
|
260
|
+
def my_dumper(data):
|
|
261
|
+
save_to_database(data)
|
|
262
|
+
|
|
263
|
+
store = SyncStore(my_loader, my_dumper)
|
|
264
|
+
store['key'] = 'value' # Calls my_dumper
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Key Classes
|
|
268
|
+
|
|
269
|
+
- **`SyncStore`** - Base class with loader/dumper functions
|
|
270
|
+
- **`FileStore`** - File-based with extension detection and key_path
|
|
271
|
+
- **`JsonStore`** - Explicit JSON with sensible defaults
|
|
272
|
+
|
|
273
|
+
|
|
210
274
|
# A few notable tools you can import from config2py
|
|
211
275
|
|
|
212
276
|
* `get_config`: Get a config value from a list of sources. See more below.
|
|
213
277
|
* `user_gettable`: Create a ``GettableContainer`` that asks the user for a value, optionally saving it.
|
|
214
278
|
* `ask_user_for_input`: Ask the user for input, optionally masking, validating and transforming the input.
|
|
215
|
-
* `
|
|
279
|
+
* `get_app_folder`: Returns the full path of a directory suitable for storing application-specific data for a given app name and folder kind (config, data, cache, state, runtime).
|
|
280
|
+
* `get_app_config_folder`: Specialized version of `get_app_folder` for configuration files.
|
|
281
|
+
* `get_app_data_folder`: Specialized version of `get_app_folder` for application data.
|
|
216
282
|
* `get_configs_local_store`: Get a local store (mapping interface of local files) of configs for a given app or package name
|
|
217
283
|
* `configs`: A default store instance for configs, defaulting to a local store under a default configuration local directory.
|
|
218
284
|
|
|
@@ -335,7 +401,3 @@ s['SOME_KEY']
|
|
|
335
401
|
|
|
336
402
|
More on that another day...
|
|
337
403
|
|
|
338
|
-
|
|
339
|
-
```python
|
|
340
|
-
|
|
341
|
-
```
|
|
@@ -16,8 +16,11 @@ from config2py.util import (
|
|
|
16
16
|
envvar, # os.environ, but with dict display override to hide secrets
|
|
17
17
|
ask_user_for_input,
|
|
18
18
|
get_app_config_folder,
|
|
19
|
+
get_app_data_folder,
|
|
20
|
+
get_app_folder,
|
|
19
21
|
get_configs_folder_for_app,
|
|
20
22
|
is_repl,
|
|
21
23
|
parse_assignments_from_py_source,
|
|
22
24
|
process_path,
|
|
23
25
|
)
|
|
26
|
+
from config2py.sync_store import SyncStore, FileStore, JsonStore, register_extension
|
|
@@ -4,26 +4,24 @@ Base for getting configs from various sources and formats
|
|
|
4
4
|
|
|
5
5
|
from collections import ChainMap
|
|
6
6
|
from typing import (
|
|
7
|
-
Callable,
|
|
8
7
|
Type,
|
|
9
8
|
Tuple,
|
|
10
9
|
KT,
|
|
11
10
|
VT,
|
|
12
11
|
Any,
|
|
13
|
-
Iterable,
|
|
14
12
|
Protocol,
|
|
15
13
|
Union,
|
|
16
14
|
runtime_checkable,
|
|
17
15
|
Optional,
|
|
18
|
-
MutableMapping,
|
|
19
16
|
)
|
|
17
|
+
from collections.abc import Callable, Iterable, MutableMapping
|
|
20
18
|
from dataclasses import dataclass
|
|
21
19
|
from functools import lru_cache, partial
|
|
22
20
|
|
|
23
21
|
from config2py.util import always_true, ask_user_for_input, no_default, not_found
|
|
24
22
|
from config2py.errors import ConfigNotFound
|
|
25
23
|
|
|
26
|
-
Exceptions =
|
|
24
|
+
Exceptions = tuple[type[Exception], ...]
|
|
27
25
|
|
|
28
26
|
|
|
29
27
|
@runtime_checkable
|
|
@@ -104,8 +102,8 @@ def get_config(
|
|
|
104
102
|
sources: Sources = None,
|
|
105
103
|
*,
|
|
106
104
|
default: VT = no_default,
|
|
107
|
-
egress:
|
|
108
|
-
val_is_valid:
|
|
105
|
+
egress: GetConfigEgress | None = None,
|
|
106
|
+
val_is_valid: Callable[[VT], bool] | None = always_true,
|
|
109
107
|
config_not_found_exceptions: Exceptions = (Exception,),
|
|
110
108
|
):
|
|
111
109
|
"""Get a config value from a list of sources
|
|
@@ -374,7 +372,7 @@ def ask_user_for_key(
|
|
|
374
372
|
save_to: SaveTo = None,
|
|
375
373
|
save_condition=is_not_empty,
|
|
376
374
|
user_asker=ask_user_for_input,
|
|
377
|
-
egress:
|
|
375
|
+
egress: Callable | None = None,
|
|
378
376
|
):
|
|
379
377
|
if key is None:
|
|
380
378
|
return partial(
|
|
@@ -399,7 +397,7 @@ def user_gettable(
|
|
|
399
397
|
save_to: SaveTo = None,
|
|
400
398
|
*,
|
|
401
399
|
prompt_template="Enter a value for {}: ",
|
|
402
|
-
egress:
|
|
400
|
+
egress: Callable | None = None,
|
|
403
401
|
user_asker=ask_user_for_input,
|
|
404
402
|
val_is_valid: Callable[[VT], bool] = is_not_empty,
|
|
405
403
|
config_not_found_exceptions: Exceptions = (Exception,),
|
|
@@ -299,11 +299,11 @@ class ConfigStore(ConfigParserStore):
|
|
|
299
299
|
|
|
300
300
|
@persist_after_operation
|
|
301
301
|
def __setitem__(self, k, v):
|
|
302
|
-
super(
|
|
302
|
+
super().__setitem__(k, v)
|
|
303
303
|
|
|
304
304
|
@persist_after_operation
|
|
305
305
|
def __delitem__(self, k):
|
|
306
|
-
super(
|
|
306
|
+
super().__delitem__(k)
|
|
307
307
|
|
|
308
308
|
# __setitem__ = super_and_persist(ConfigParser, '__setitem__')
|
|
309
309
|
# __delitem__ = super_and_persist(ConfigParser, '__delitem__')
|
|
@@ -388,13 +388,14 @@ class ConfigReader(ConfigStore):
|
|
|
388
388
|
# return super()._obj_of_data(data)
|
|
389
389
|
|
|
390
390
|
|
|
391
|
-
from typing import
|
|
391
|
+
from typing import Union
|
|
392
|
+
from collections.abc import Mapping, Iterable, Generator
|
|
392
393
|
import re
|
|
393
394
|
|
|
394
395
|
|
|
395
396
|
# TODO: postprocess_ini_section_items and preprocess_ini_section_items: Add comma separated possibility?
|
|
396
397
|
# TODO: Find out if configparse has an option to do this processing alreadys
|
|
397
|
-
def postprocess_ini_section_items(items:
|
|
398
|
+
def postprocess_ini_section_items(items: Mapping | Iterable) -> Generator:
|
|
398
399
|
r"""Transform newline-separated string values into actual list of strings (assuming that intent)
|
|
399
400
|
|
|
400
401
|
>>> section_from_ini = {
|
|
@@ -417,7 +418,7 @@ def postprocess_ini_section_items(items: Union[Mapping, Iterable]) -> Generator:
|
|
|
417
418
|
|
|
418
419
|
|
|
419
420
|
# TODO: Find out if configparse has an option to do this processing alreadys
|
|
420
|
-
def preprocess_ini_section_items(items:
|
|
421
|
+
def preprocess_ini_section_items(items: Mapping | Iterable) -> Generator:
|
|
421
422
|
"""Transform list values into newline-separated strings, in view of writing the value to a ini formatted section
|
|
422
423
|
>>> section = {
|
|
423
424
|
... 'name': 'aspyre',
|