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 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, config_not_found)
219
- if value is config_not_found:
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
- # Note: The only purpose of the cache is to avoid calling the getter function
296
- # twice when doing a ``in`` check before doing a ``[]`` lookup, for instance,
297
- # in a``collections.ChainMap``.
298
- self.getter = lru_cache(maxsize=1)(self.getter)
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] = always_true,
404
+ val_is_valid: Callable[[VT], bool] = is_not_empty,
407
405
  config_not_found_exceptions: Exceptions = (Exception,),
408
406
  ):
409
407
  """
@@ -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):
@@ -1,6 +1,7 @@
1
1
  import tempfile
2
2
  import os
3
3
  import pytest
4
+ from config2py.util import process_path
4
5
 
5
6
 
6
7
  def test_process_path():
@@ -0,0 +1,5 @@
1
+ from functools import partial
2
+
3
+
4
+ def user_input_patch(monkeypatch, user_input_string: str):
5
+ monkeypatch.setattr('builtins.input', lambda _: user_input_string)
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 = None,
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: config2py
3
- Version: 0.1.34
3
+ Version: 0.1.35
4
4
  Summary: Simplified reading and writing configurations from various sources and formats
5
5
  Home-page: https://github.com/i2mint/config2py
6
6
  Author: OtoSense
@@ -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,,