config2py 0.1.34__py3-none-any.whl → 0.1.35__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/base.py +15 -17
- config2py/tests/test_tools.py +44 -0
- config2py/tests/{util.py → test_util.py} +1 -0
- config2py/tests/utils_for_testing.py +5 -0
- config2py/util.py +10 -2
- {config2py-0.1.34.dist-info → config2py-0.1.35.dist-info}/METADATA +1 -1
- config2py-0.1.35.dist-info/RECORD +16 -0
- config2py-0.1.34.dist-info/RECORD +0 -15
- {config2py-0.1.34.dist-info → config2py-0.1.35.dist-info}/LICENSE +0 -0
- {config2py-0.1.34.dist-info → config2py-0.1.35.dist-info}/WHEEL +0 -0
- {config2py-0.1.34.dist-info → config2py-0.1.35.dist-info}/top_level.txt +0 -0
config2py/base.py
CHANGED
|
@@ -19,14 +19,10 @@ from typing import (
|
|
|
19
19
|
)
|
|
20
20
|
from dataclasses import dataclass
|
|
21
21
|
from functools import lru_cache, partial
|
|
22
|
-
from i2 import mk_sentinel # TODO: Only i2 dependency. Consider replacing.
|
|
23
22
|
|
|
24
|
-
from config2py.util import always_true, ask_user_for_input
|
|
23
|
+
from config2py.util import always_true, ask_user_for_input, no_default, not_found
|
|
25
24
|
from config2py.errors import ConfigNotFound
|
|
26
25
|
|
|
27
|
-
# def mk_sentinel(name): # TODO: Only i2 dependency. Here's replacement, but not picklable
|
|
28
|
-
# return type(name, (), {'__repr__': lambda self: name})()
|
|
29
|
-
|
|
30
26
|
Exceptions = Tuple[Type[Exception], ...]
|
|
31
27
|
|
|
32
28
|
|
|
@@ -95,9 +91,6 @@ GetConfigEgress = Callable[[KT, VT], VT]
|
|
|
95
91
|
# )
|
|
96
92
|
# openai.api_key = _api_key
|
|
97
93
|
|
|
98
|
-
config_not_found = mk_sentinel('config_not_found')
|
|
99
|
-
no_default = mk_sentinel('no_default')
|
|
100
|
-
|
|
101
94
|
|
|
102
95
|
def is_not_none_nor_empty(x):
|
|
103
96
|
if isinstance(x, str):
|
|
@@ -111,8 +104,8 @@ def get_config(
|
|
|
111
104
|
sources: Sources = None,
|
|
112
105
|
*,
|
|
113
106
|
default: VT = no_default,
|
|
114
|
-
egress: GetConfigEgress = None,
|
|
115
|
-
val_is_valid: Callable[[VT], bool] = always_true,
|
|
107
|
+
egress: Optional[GetConfigEgress] = None,
|
|
108
|
+
val_is_valid: Optional[Callable[[VT], bool]] = always_true,
|
|
116
109
|
config_not_found_exceptions: Exceptions = (Exception,),
|
|
117
110
|
):
|
|
118
111
|
"""Get a config value from a list of sources
|
|
@@ -215,8 +208,8 @@ def get_config(
|
|
|
215
208
|
get_config_.sources = sources
|
|
216
209
|
return get_config_
|
|
217
210
|
chain_map = sources_chainmap(sources, val_is_valid, config_not_found_exceptions)
|
|
218
|
-
value = chain_map.get(key,
|
|
219
|
-
if value is
|
|
211
|
+
value = chain_map.get(key, not_found)
|
|
212
|
+
if value is not_found:
|
|
220
213
|
if default is no_default:
|
|
221
214
|
raise ConfigNotFound(f'Could not find config for key: {key}')
|
|
222
215
|
else:
|
|
@@ -290,12 +283,16 @@ class FuncBasedGettableContainer:
|
|
|
290
283
|
getter: Callable[[KT], VT]
|
|
291
284
|
val_is_valid: Callable[[VT], bool] = always_true
|
|
292
285
|
config_not_found_exceptions: Exceptions = (Exception,)
|
|
286
|
+
cache_getter = False
|
|
293
287
|
|
|
294
288
|
def __post_init__(self):
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
289
|
+
if self.cache_getter:
|
|
290
|
+
# Note: The only purpose of the cache is to avoid calling the getter function
|
|
291
|
+
# twice when doing a ``in`` check before doing a ``[]`` lookup, for instance,
|
|
292
|
+
# in a``collections.ChainMap``.
|
|
293
|
+
# But this caching can lead to unexpected behavior if the getter function
|
|
294
|
+
# has side effects, or if the value it returns changes over time.
|
|
295
|
+
self.getter = lru_cache(maxsize=1)(self.getter)
|
|
299
296
|
|
|
300
297
|
def __getitem__(self, k: KT) -> VT:
|
|
301
298
|
try:
|
|
@@ -384,6 +381,7 @@ def ask_user_for_key(
|
|
|
384
381
|
ask_user_for_key,
|
|
385
382
|
prompt_template=prompt_template,
|
|
386
383
|
save_to=save_to,
|
|
384
|
+
save_condition=save_condition,
|
|
387
385
|
user_asker=user_asker,
|
|
388
386
|
egress=egress,
|
|
389
387
|
)
|
|
@@ -403,7 +401,7 @@ def user_gettable(
|
|
|
403
401
|
prompt_template='Enter a value for {}: ',
|
|
404
402
|
egress: Optional[Callable] = None,
|
|
405
403
|
user_asker=ask_user_for_input,
|
|
406
|
-
val_is_valid: Callable[[VT], bool] =
|
|
404
|
+
val_is_valid: Callable[[VT], bool] = is_not_empty,
|
|
407
405
|
config_not_found_exceptions: Exceptions = (Exception,),
|
|
408
406
|
):
|
|
409
407
|
"""
|
config2py/tests/test_tools.py
CHANGED
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
import os
|
|
4
4
|
from unittest.mock import patch
|
|
5
5
|
|
|
6
|
+
import tempfile
|
|
6
7
|
import pytest
|
|
7
8
|
|
|
8
9
|
from config2py.tools import simple_config_getter, source_config_params
|
|
10
|
+
from config2py.tests.utils_for_testing import user_input_patch
|
|
9
11
|
|
|
10
12
|
|
|
11
13
|
@pytest.fixture
|
|
@@ -41,6 +43,48 @@ def test_simple_config_getter(mock_config_store_factory):
|
|
|
41
43
|
# assert config_getter.configs is mock_config_store
|
|
42
44
|
|
|
43
45
|
|
|
46
|
+
from functools import partial
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_simple_config_getter_with_user_input(monkeypatch):
|
|
50
|
+
user_inputs = partial(user_input_patch, monkeypatch)
|
|
51
|
+
local_config_dir = tempfile.mkdtemp()
|
|
52
|
+
|
|
53
|
+
# Note, at the time of writing this, the default is ask_user_if_key_not_found=None,
|
|
54
|
+
# which has the effect of NOT asking the user for a config value if we're not in
|
|
55
|
+
# a REPL (interactive mode). Therefore, the test worked in the notebook, but not here.
|
|
56
|
+
# So now, we're forcing ask_user_if_key_not_found=True
|
|
57
|
+
my_get_config = simple_config_getter(
|
|
58
|
+
local_config_dir, ask_user_if_key_not_found=True
|
|
59
|
+
)
|
|
60
|
+
config_name = 'SOME_CONFIG_NAME'
|
|
61
|
+
|
|
62
|
+
# make sure config_name not in environment or local_config_dir
|
|
63
|
+
assert config_name not in os.environ
|
|
64
|
+
assert config_name not in os.listdir(local_config_dir)
|
|
65
|
+
|
|
66
|
+
# since config_name doesn't exist, the following attempt to get this config
|
|
67
|
+
# should try to get it from the user
|
|
68
|
+
|
|
69
|
+
# Use monkeypatch to replace the input function with the mock_input function
|
|
70
|
+
user_inputs('') # user enters nothing
|
|
71
|
+
val = my_get_config(config_name, default='default_value')
|
|
72
|
+
|
|
73
|
+
# This the user didn't enter anything, the default value should be returned:
|
|
74
|
+
assert val == 'default_value'
|
|
75
|
+
|
|
76
|
+
# Still no config_name in the local_config_dir
|
|
77
|
+
assert config_name not in os.listdir(local_config_dir)
|
|
78
|
+
|
|
79
|
+
user_inputs('user_value') # user enters user_value
|
|
80
|
+
val = my_get_config(config_name, default='default_value')
|
|
81
|
+
# Now the user entered a value, so that value should be returned:
|
|
82
|
+
assert val == 'user_value'
|
|
83
|
+
|
|
84
|
+
# And now there's a config_name in the local_config_dir
|
|
85
|
+
assert config_name in os.listdir(local_config_dir)
|
|
86
|
+
|
|
87
|
+
|
|
44
88
|
def test_source_config_params():
|
|
45
89
|
@source_config_params('a', 'b')
|
|
46
90
|
def foo(a, b, c):
|
config2py/util.py
CHANGED
|
@@ -7,9 +7,17 @@ from pathlib import Path
|
|
|
7
7
|
from typing import Optional, Union, Any, Callable, Set, Iterable
|
|
8
8
|
import getpass
|
|
9
9
|
|
|
10
|
+
from i2 import mk_sentinel # TODO: Only i2 dependency. Consider replacing.
|
|
11
|
+
|
|
12
|
+
# def mk_sentinel(name): # TODO: Only i2 dependency. Here's replacement, but not picklable
|
|
13
|
+
# return type(name, (), {'__repr__': lambda self: name})()
|
|
14
|
+
|
|
10
15
|
DFLT_APP_NAME = 'config2py'
|
|
11
16
|
DFLT_MASKING_INPUT = False
|
|
12
17
|
|
|
18
|
+
not_found = mk_sentinel('not_found')
|
|
19
|
+
no_default = mk_sentinel('no_default')
|
|
20
|
+
|
|
13
21
|
|
|
14
22
|
def always_true(x: Any) -> bool:
|
|
15
23
|
"""Function that just returns True."""
|
|
@@ -29,7 +37,7 @@ def is_not_empty(x: Any) -> bool:
|
|
|
29
37
|
# TODO: Make this into an open-closed mini-framework
|
|
30
38
|
def ask_user_for_input(
|
|
31
39
|
prompt: str,
|
|
32
|
-
default: str =
|
|
40
|
+
default: str = '',
|
|
33
41
|
*,
|
|
34
42
|
mask_input=DFLT_MASKING_INPUT,
|
|
35
43
|
masking_toggle_str: str = None,
|
|
@@ -57,7 +65,7 @@ def ask_user_for_input(
|
|
|
57
65
|
f" (Input masking is {'ENABLED' if mask_input else 'DISABLED'}. "
|
|
58
66
|
f"Enter '{masking_toggle_str}' (without quotes) to toggle input masking)\n"
|
|
59
67
|
)
|
|
60
|
-
if default:
|
|
68
|
+
if default not in {''}:
|
|
61
69
|
prompt = prompt + f' [{default}]: '
|
|
62
70
|
if mask_input:
|
|
63
71
|
_prompt_func = getpass.getpass
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
config2py/__init__.py,sha256=ru-bUQk5EuOLzHmNAcElSN8P2saSUEb5KFRkfTkzsUo,770
|
|
2
|
+
config2py/base.py,sha256=fDKClGpgNecWFL9a7Y_FpqwuRxAAD_XCuByW68Yq5KE,15880
|
|
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=W2YQm-PerKRN8prsxVfcBCChx75pZ9H8UqWH8pGxECE,9191
|
|
6
|
+
config2py/util.py,sha256=C9JiGlMcSQozq6H-nuaS02sh49i4irRzSVqofW9Bq74,16460
|
|
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=km9RNh-gDtSJNjYiLQdkWcRi9IB5MwpijD3U2XCyi1Y,3728
|
|
10
|
+
config2py/tests/test_util.py,sha256=DNJn60dvyr7xb86Spoz_VDS4cwU2mtcRo1_MgoCrKCY,1391
|
|
11
|
+
config2py/tests/utils_for_testing.py,sha256=Vz6EDY27uy_RZCSceZ7jqXkp_CXe52KAZSXcYKivazM,162
|
|
12
|
+
config2py-0.1.35.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
13
|
+
config2py-0.1.35.dist-info/METADATA,sha256=LN--FD2SE3BMV8vAfAq5CUSaksGY5DbXKw8qyZagzbs,14559
|
|
14
|
+
config2py-0.1.35.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
15
|
+
config2py-0.1.35.dist-info/top_level.txt,sha256=DFnlOIKMIGWQRROr3voJFhWFViHaWgTTeWZjC5YC9QQ,10
|
|
16
|
+
config2py-0.1.35.dist-info/RECORD,,
|
|
@@ -1,15 +0,0 @@
|
|
|
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=W2YQm-PerKRN8prsxVfcBCChx75pZ9H8UqWH8pGxECE,9191
|
|
6
|
-
config2py/util.py,sha256=cYD2O1FCYelvYsjy-bODxuQpR-NvgbCi9YEVBiQi4c4,16140
|
|
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.34.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
12
|
-
config2py-0.1.34.dist-info/METADATA,sha256=iy8k91WeXwgGBoMCLcovds5WdIKmcqyRi5QACQ6Gi5s,14559
|
|
13
|
-
config2py-0.1.34.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
14
|
-
config2py-0.1.34.dist-info/top_level.txt,sha256=DFnlOIKMIGWQRROr3voJFhWFViHaWgTTeWZjC5YC9QQ,10
|
|
15
|
-
config2py-0.1.34.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|