config2py 0.1.27__py3-none-any.whl → 0.1.29__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- config2py/__init__.py +1 -0
- config2py/base.py +23 -5
- config2py/tools.py +1 -0
- config2py/util.py +26 -9
- {config2py-0.1.27.dist-info → config2py-0.1.29.dist-info}/METADATA +1 -1
- config2py-0.1.29.dist-info/RECORD +15 -0
- {config2py-0.1.27.dist-info → config2py-0.1.29.dist-info}/WHEEL +1 -1
- config2py-0.1.27.dist-info/RECORD +0 -15
- {config2py-0.1.27.dist-info → config2py-0.1.29.dist-info}/LICENSE +0 -0
- {config2py-0.1.27.dist-info → config2py-0.1.29.dist-info}/top_level.txt +0 -0
config2py/__init__.py
CHANGED
config2py/base.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Base for getting configs from various sources and formats
|
|
3
3
|
"""
|
|
4
|
+
|
|
4
5
|
from collections import ChainMap
|
|
5
6
|
from typing import (
|
|
6
7
|
Callable,
|
|
@@ -317,6 +318,15 @@ class FuncBasedGettableContainer:
|
|
|
317
318
|
else:
|
|
318
319
|
return True
|
|
319
320
|
|
|
321
|
+
def __iter__(self):
|
|
322
|
+
raise TypeError(
|
|
323
|
+
f'Iteration is NOT ACTUALLY implemented for {type(self).__name__}. '
|
|
324
|
+
'The reason we still stuck it in the class is because python will '
|
|
325
|
+
'automatically try to use __getitem__ on 0, 1, ... to iterate over the '
|
|
326
|
+
'object, and we want to avoid that, since this would result in an obscure '
|
|
327
|
+
"error message saying that the getter function can't be called on 0"
|
|
328
|
+
)
|
|
329
|
+
|
|
320
330
|
|
|
321
331
|
def gettable_containers(
|
|
322
332
|
sources: Sources,
|
|
@@ -353,11 +363,19 @@ KTSaver = Callable[[KT, VT], Any]
|
|
|
353
363
|
SaveTo = Optional[Union[MutableMapping, KTSaver]]
|
|
354
364
|
|
|
355
365
|
|
|
366
|
+
def is_not_empty(val) -> bool:
|
|
367
|
+
if isinstance(val, str):
|
|
368
|
+
return val != ''
|
|
369
|
+
else:
|
|
370
|
+
return val is not None
|
|
371
|
+
|
|
372
|
+
|
|
356
373
|
def ask_user_for_key(
|
|
357
374
|
key=None,
|
|
358
375
|
*,
|
|
359
376
|
prompt_template='Enter a value for {}: ',
|
|
360
377
|
save_to: SaveTo = None,
|
|
378
|
+
save_condition=is_not_empty,
|
|
361
379
|
user_asker=ask_user_for_input,
|
|
362
380
|
egress: Optional[Callable] = None,
|
|
363
381
|
):
|
|
@@ -372,7 +390,7 @@ def ask_user_for_key(
|
|
|
372
390
|
val = user_asker(prompt_template.format(key))
|
|
373
391
|
if isinstance(egress, Callable):
|
|
374
392
|
val = egress(key, val)
|
|
375
|
-
if save_to is not None:
|
|
393
|
+
if save_to is not None and save_condition(val):
|
|
376
394
|
if hasattr(save_to, '__setitem__'):
|
|
377
395
|
save_to_func = save_to.__setitem__
|
|
378
396
|
save_to_func(key, val)
|
|
@@ -397,7 +415,7 @@ def user_gettable(
|
|
|
397
415
|
contain a placeholder for the key, e.g. ``"Enter a value for {}: "``.
|
|
398
416
|
:param egress: A function to apply to the user's response before returning it.
|
|
399
417
|
This can be used to validate the response, for example.
|
|
400
|
-
:param user_asker: A function that asks the user for input. It should take a
|
|
418
|
+
:param user_asker: A function that asks the user for input. It should take a
|
|
401
419
|
prompt string and return the user's response.
|
|
402
420
|
:param val_is_valid: A function that takes a value and returns a boolean. If it
|
|
403
421
|
returns ``False``, the user will be asked for a new value.
|
|
@@ -408,7 +426,7 @@ def user_gettable(
|
|
|
408
426
|
it.
|
|
409
427
|
|
|
410
428
|
Example:
|
|
411
|
-
|
|
429
|
+
|
|
412
430
|
>>> s = user_gettable()
|
|
413
431
|
>>> v = s['SOME_KEY'] # doctest: +SKIP
|
|
414
432
|
'SOME_VAL'
|
|
@@ -416,8 +434,8 @@ def user_gettable(
|
|
|
416
434
|
This will trigger a prompt for the user to enter the value of ``SOME_KEY``.
|
|
417
435
|
When they do (say they entered 'SOME_VAL') it will return that value.
|
|
418
436
|
|
|
419
|
-
And if you specify a save_to store (usually a persistent MutableMapping made with
|
|
420
|
-
the ``dol`` package) then it will save the value to that store for future use.
|
|
437
|
+
And if you specify a save_to store (usually a persistent MutableMapping made with
|
|
438
|
+
the ``dol`` package) then it will save the value to that store for future use.
|
|
421
439
|
|
|
422
440
|
>>> d = dict(some='store')
|
|
423
441
|
>>> s = user_gettable(save_to=d)
|
config2py/tools.py
CHANGED
|
@@ -37,6 +37,7 @@ def get_configs_local_store(
|
|
|
37
37
|
from config2py.s_configparser import ConfigStore
|
|
38
38
|
|
|
39
39
|
return ConfigStore(config_src)
|
|
40
|
+
|
|
40
41
|
elif os.path.sep not in config_src: # it's just a string
|
|
41
42
|
# TODO: is "get" the right word, since it makes the folder too
|
|
42
43
|
path = get_configs_folder_for_app(config_src, configs_name=configs_name)
|
config2py/util.py
CHANGED
|
@@ -4,7 +4,7 @@ import re
|
|
|
4
4
|
import os
|
|
5
5
|
import ast
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from typing import Optional, Union, Any, Callable, Set
|
|
7
|
+
from typing import Optional, Union, Any, Callable, Set, Iterable
|
|
8
8
|
import getpass
|
|
9
9
|
|
|
10
10
|
DFLT_APP_NAME = 'config2py'
|
|
@@ -21,6 +21,11 @@ def identity(x: Any) -> Any:
|
|
|
21
21
|
return x
|
|
22
22
|
|
|
23
23
|
|
|
24
|
+
def is_not_empty(x: Any) -> bool:
|
|
25
|
+
"""Function that returns True if x is not empty."""
|
|
26
|
+
return bool(x)
|
|
27
|
+
|
|
28
|
+
|
|
24
29
|
# TODO: Make this into an open-closed mini-framework
|
|
25
30
|
def ask_user_for_input(
|
|
26
31
|
prompt: str,
|
|
@@ -55,9 +60,12 @@ def ask_user_for_input(
|
|
|
55
60
|
if default:
|
|
56
61
|
prompt = prompt + f' [{default}]: '
|
|
57
62
|
if mask_input:
|
|
58
|
-
|
|
63
|
+
_prompt_func = getpass.getpass
|
|
59
64
|
else:
|
|
60
|
-
|
|
65
|
+
_prompt_func = input
|
|
66
|
+
|
|
67
|
+
response = _prompt_func(prompt)
|
|
68
|
+
|
|
61
69
|
if masking_toggle_str is not None and response == masking_toggle_str:
|
|
62
70
|
return ask_user_for_input(
|
|
63
71
|
_original_prompt,
|
|
@@ -168,19 +176,19 @@ DFLT_APP_DATA_FOLDER = os.getenv(
|
|
|
168
176
|
|
|
169
177
|
|
|
170
178
|
def process_path(
|
|
171
|
-
path: str,
|
|
172
|
-
*,
|
|
179
|
+
*path: Iterable[str],
|
|
173
180
|
ensure_dir_exists=False,
|
|
174
181
|
assert_exists=False,
|
|
175
182
|
ensure_endswith_slash=False,
|
|
176
183
|
ensure_does_not_end_with_slash=False,
|
|
177
184
|
expanduser=True,
|
|
185
|
+
rootdir: str = '',
|
|
178
186
|
) -> str:
|
|
179
187
|
"""
|
|
180
188
|
Process a path string, ensuring it exists, and optionally expanding user.
|
|
181
189
|
|
|
182
190
|
Args:
|
|
183
|
-
path (str): The path to process.
|
|
191
|
+
path (Iterable[str]): The path to process. Can be multiple components of a path.
|
|
184
192
|
ensure_dir_exists (bool): Whether to ensure the path exists.
|
|
185
193
|
assert_exists (bool): Whether to assert that the path exists.
|
|
186
194
|
ensure_endswith_slash (bool): Whether to ensure the path ends with a slash.
|
|
@@ -190,11 +198,20 @@ def process_path(
|
|
|
190
198
|
Returns:
|
|
191
199
|
str: The processed path.
|
|
192
200
|
|
|
201
|
+
>>> process_path('a', 'b', 'c')
|
|
202
|
+
'a/b/c'
|
|
203
|
+
>>> from functools import partial
|
|
204
|
+
>>> process_path('a', 'b', 'c', rootdir='/root/dir/', ensure_endswith_slash=True)
|
|
205
|
+
'/root/dir/a/b/c/'
|
|
206
|
+
|
|
193
207
|
"""
|
|
208
|
+
path = os.path.join(*path)
|
|
194
209
|
if ensure_endswith_slash and ensure_does_not_end_with_slash:
|
|
195
210
|
raise ValueError(
|
|
196
211
|
'Cannot ensure both ends with slash and does not end with slash.'
|
|
197
212
|
)
|
|
213
|
+
if rootdir:
|
|
214
|
+
path = os.path.join(rootdir, path)
|
|
198
215
|
if expanduser:
|
|
199
216
|
path = os.path.expanduser(path)
|
|
200
217
|
if ensure_endswith_slash:
|
|
@@ -283,8 +300,8 @@ def get_app_data_folder(
|
|
|
283
300
|
>>> get_app_data_folder() # doctest: +ELLIPSIS
|
|
284
301
|
'.../.config/config2py'
|
|
285
302
|
|
|
286
|
-
You can specify a different app name though.
|
|
287
|
-
And if you want, you can also specify a callback function to initialize the
|
|
303
|
+
You can specify a different app name though.
|
|
304
|
+
And if you want, you can also specify a callback function to initialize the
|
|
288
305
|
directory.
|
|
289
306
|
|
|
290
307
|
>>> path = get_app_data_folder('my_app', ensure_exists=True) # doctest: +SKIP
|
|
@@ -303,7 +320,7 @@ def get_app_data_folder(
|
|
|
303
320
|
get_app_data_rootdir(ensure_exists=ensure_exists), app_name
|
|
304
321
|
)
|
|
305
322
|
app_data_folder_did_not_exist = not os.path.isdir(app_data_path)
|
|
306
|
-
|
|
323
|
+
process_path(app_data_path, ensure_dir_exists=True)
|
|
307
324
|
|
|
308
325
|
if app_data_folder_did_not_exist:
|
|
309
326
|
setup_callback(app_data_path)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
config2py/__init__.py,sha256=ru-bUQk5EuOLzHmNAcElSN8P2saSUEb5KFRkfTkzsUo,770
|
|
2
|
+
config2py/base.py,sha256=saq1YVbeRg8jBoXcmc0y-maNJ5vHHeWwu5tc_EbOetE,15895
|
|
3
|
+
config2py/errors.py,sha256=QdwGsoJhv6LHDHp-_yyz4oUg1Fgu4S-S7O2nuA0a5cw,203
|
|
4
|
+
config2py/s_configparser.py,sha256=XhxFz6-PG4-QsecJfbjLFdBWHcPU6dwgqwkTZyY_y3E,15873
|
|
5
|
+
config2py/tools.py,sha256=k-PcyvcWFaO_vQn9gbkHf41CDh_sEtZYAWGRno7xsxo,8425
|
|
6
|
+
config2py/util.py,sha256=XW0kIu8MulBx9WbnEBJnByZ-wv08vqFJRtm2mzkRwok,15729
|
|
7
|
+
config2py/scrap/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
config2py/tests/__init__.py,sha256=sk-yGJQOZES2z70M4xmZB57tsxSktX_84ybDuV8Cz5Q,297
|
|
9
|
+
config2py/tests/test_tools.py,sha256=T0rBy8s6wHgQXnnr7Z1xkF1so3XkdGVASerEQ27ByxE,1950
|
|
10
|
+
config2py/tests/util.py,sha256=vO1VIepbH6vY2e-VHP7HX6jnVzzIDyFsp6md_uBnIXw,1351
|
|
11
|
+
config2py-0.1.29.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
12
|
+
config2py-0.1.29.dist-info/METADATA,sha256=-Rdx2t-wJ1rxNGDzXlNuBz4FsWyhOyR-a4oNLtPe6s4,14559
|
|
13
|
+
config2py-0.1.29.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
14
|
+
config2py-0.1.29.dist-info/top_level.txt,sha256=DFnlOIKMIGWQRROr3voJFhWFViHaWgTTeWZjC5YC9QQ,10
|
|
15
|
+
config2py-0.1.29.dist-info/RECORD,,
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
config2py/__init__.py,sha256=pIRwstYdRf794ew7o8zgb057dV-RSMi7uQYiod2IhHU,752
|
|
2
|
-
config2py/base.py,sha256=hqXR0KeCsss24X7O_rxklWVBw-Gea1uUY7K-0ByNZvo,15244
|
|
3
|
-
config2py/errors.py,sha256=QdwGsoJhv6LHDHp-_yyz4oUg1Fgu4S-S7O2nuA0a5cw,203
|
|
4
|
-
config2py/s_configparser.py,sha256=XhxFz6-PG4-QsecJfbjLFdBWHcPU6dwgqwkTZyY_y3E,15873
|
|
5
|
-
config2py/tools.py,sha256=G31ed8MrCPIMFNUuXeIkSQVWnfRgdK94d7Dmtg1vrDY,8424
|
|
6
|
-
config2py/util.py,sha256=3nUgRmzJg7sXnutB1rR8BRHhRmJ9t_7xYCGWvwr1HOM,15237
|
|
7
|
-
config2py/scrap/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
config2py/tests/__init__.py,sha256=sk-yGJQOZES2z70M4xmZB57tsxSktX_84ybDuV8Cz5Q,297
|
|
9
|
-
config2py/tests/test_tools.py,sha256=T0rBy8s6wHgQXnnr7Z1xkF1so3XkdGVASerEQ27ByxE,1950
|
|
10
|
-
config2py/tests/util.py,sha256=vO1VIepbH6vY2e-VHP7HX6jnVzzIDyFsp6md_uBnIXw,1351
|
|
11
|
-
config2py-0.1.27.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
12
|
-
config2py-0.1.27.dist-info/METADATA,sha256=HGBgqpSEVM4Afh_RhF1e4uvFVNK5Zg29Mna_sn--kO8,14559
|
|
13
|
-
config2py-0.1.27.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
|
14
|
-
config2py-0.1.27.dist-info/top_level.txt,sha256=DFnlOIKMIGWQRROr3voJFhWFViHaWgTTeWZjC5YC9QQ,10
|
|
15
|
-
config2py-0.1.27.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|