byoconfig 0.0.1__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.
- byoconfig-0.0.1/PKG-INFO +24 -0
- byoconfig-0.0.1/README.md +0 -0
- byoconfig-0.0.1/byo_config/__init__.py +4 -0
- byoconfig-0.0.1/byo_config/config.py +112 -0
- byoconfig-0.0.1/byo_config/error.py +19 -0
- byoconfig-0.0.1/byoconfig.egg-info/PKG-INFO +24 -0
- byoconfig-0.0.1/byoconfig.egg-info/SOURCES.txt +15 -0
- byoconfig-0.0.1/byoconfig.egg-info/dependency_links.txt +1 -0
- byoconfig-0.0.1/byoconfig.egg-info/requires.txt +10 -0
- byoconfig-0.0.1/byoconfig.egg-info/top_level.txt +1 -0
- byoconfig-0.0.1/pyproject.toml +67 -0
- byoconfig-0.0.1/setup.cfg +4 -0
- byoconfig-0.0.1/tests/test_config.py +56 -0
- byoconfig-0.0.1/tests/test_error.py +0 -0
- byoconfig-0.0.1/tests/test_sources_base.py +96 -0
- byoconfig-0.0.1/tests/test_sources_env.py +62 -0
- byoconfig-0.0.1/tests/test_sources_file.py +112 -0
byoconfig-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: byoconfig
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A configuration class that supports plugins, multiple file formats, heirarchical configuration, and more.
|
|
5
|
+
Author-email: Cam Ratchford <camratchford@gmail.com>
|
|
6
|
+
Classifier: Development Status :: 3 - Alpha
|
|
7
|
+
Classifier: Programming Language :: Python
|
|
8
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Intended Audience :: Information Technology
|
|
11
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
12
|
+
Classifier: Environment :: Win32 (MS Windows)
|
|
13
|
+
Classifier: Environment :: MacOS X
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
|
+
Requires-Python: >=3.8
|
|
16
|
+
Requires-Dist: PyYAML==6.0.2
|
|
17
|
+
Requires-Dist: toml==0.10.2
|
|
18
|
+
Provides-Extra: development
|
|
19
|
+
Requires-Dist: mkdocs; extra == "development"
|
|
20
|
+
Requires-Dist: mkdocs-material; extra == "development"
|
|
21
|
+
Requires-Dist: ruff; extra == "development"
|
|
22
|
+
Requires-Dist: pytest; extra == "development"
|
|
23
|
+
Requires-Dist: build; extra == "development"
|
|
24
|
+
Requires-Dist: twine; extra == "development"
|
|
File without changes
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import inspect
|
|
3
|
+
from typing import Optional, Type
|
|
4
|
+
|
|
5
|
+
from byo_config.sources import (
|
|
6
|
+
BaseVariableSource,
|
|
7
|
+
FileVariableSource,
|
|
8
|
+
EnvVariableSource,
|
|
9
|
+
FileTypes,
|
|
10
|
+
)
|
|
11
|
+
from byo_config.error import BYOConfigError
|
|
12
|
+
|
|
13
|
+
__all__ = ['Config']
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Config(FileVariableSource, EnvVariableSource):
|
|
19
|
+
"""
|
|
20
|
+
A versatile config object that can parse many file types, load environment variables,
|
|
21
|
+
and load dictionary keys as class attributes.
|
|
22
|
+
- Multiple config objects from different unique sources can be collated easily with a precedence value.
|
|
23
|
+
- Config instances with higher precedence have their values overwrite the values of lower precedence
|
|
24
|
+
instances when merging objects.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
source_file_path: Optional[str] = None,
|
|
30
|
+
forced_file_type: Optional[FileTypes] = None,
|
|
31
|
+
env_prefix: Optional[str] = None,
|
|
32
|
+
var_source_name: Optional[str] = "Config",
|
|
33
|
+
precedence: Optional[int] = 1,
|
|
34
|
+
**kwargs
|
|
35
|
+
):
|
|
36
|
+
"""
|
|
37
|
+
Initialize a Config object.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
source_file_path (str):
|
|
41
|
+
The path to the source file.
|
|
42
|
+
|
|
43
|
+
forced_file_type (FileTypes):
|
|
44
|
+
The file type of the source file, if you don't want to use the file's extension.
|
|
45
|
+
|
|
46
|
+
var_source_name (str):
|
|
47
|
+
The name of the variable source.
|
|
48
|
+
|
|
49
|
+
env_prefix (str):
|
|
50
|
+
The configuration keys will be loaded from the environment variables with this prefix.
|
|
51
|
+
Use the "*" wildcard if you want to load all environment variables as configuration keys.
|
|
52
|
+
|
|
53
|
+
precedence (int):
|
|
54
|
+
The precedence of the variable source.
|
|
55
|
+
|
|
56
|
+
**kwargs:
|
|
57
|
+
Arbitrary keyword arguments, to be loaded as class attributes.
|
|
58
|
+
"""
|
|
59
|
+
try:
|
|
60
|
+
self.precedence = precedence
|
|
61
|
+
self.var_source_name = var_source_name
|
|
62
|
+
super().__init__(
|
|
63
|
+
source_file=source_file_path,
|
|
64
|
+
forced_file_type=forced_file_type
|
|
65
|
+
)
|
|
66
|
+
super().load_env(env_prefix)
|
|
67
|
+
|
|
68
|
+
self.set_data(kwargs)
|
|
69
|
+
logger.debug(f"Config object {self.var_source_name} created with precedence {self.precedence}")
|
|
70
|
+
|
|
71
|
+
except BYOConfigError as e:
|
|
72
|
+
raise e
|
|
73
|
+
|
|
74
|
+
except FileNotFoundError as e:
|
|
75
|
+
raise e
|
|
76
|
+
|
|
77
|
+
except ValueError as e:
|
|
78
|
+
raise e
|
|
79
|
+
|
|
80
|
+
except Exception as e:
|
|
81
|
+
raise e
|
|
82
|
+
|
|
83
|
+
def include(self, plugin_class: Type[BaseVariableSource], **kwargs):
|
|
84
|
+
"""
|
|
85
|
+
Include a plugin class in the config object.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
plugin_class (Type[BaseVariableSource]):
|
|
89
|
+
The plugin class to include in the config object.
|
|
90
|
+
|
|
91
|
+
**kwargs:
|
|
92
|
+
Arbitrary keyword arguments to pass to the plugin class.
|
|
93
|
+
"""
|
|
94
|
+
try:
|
|
95
|
+
# get signature of plugin class
|
|
96
|
+
sig = inspect.signature(plugin_class)
|
|
97
|
+
# Compare kwargs to signature
|
|
98
|
+
for k, v in kwargs.items():
|
|
99
|
+
if k not in sig.parameters:
|
|
100
|
+
raise BYOConfigError(
|
|
101
|
+
f"Invalid parameter '{k}' for plugin class '{plugin_class.__name__}'",
|
|
102
|
+
self
|
|
103
|
+
)
|
|
104
|
+
plugin = plugin_class(**kwargs) # type: ignore
|
|
105
|
+
self.set_data(plugin.get_data())
|
|
106
|
+
logger.debug(f"Initialized plugin '{plugin_class.__name__}' with data: {plugin.get_data()}")
|
|
107
|
+
|
|
108
|
+
except BYOConfigError as e:
|
|
109
|
+
raise e
|
|
110
|
+
|
|
111
|
+
except Exception as e:
|
|
112
|
+
raise e
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
|
|
2
|
+
__all__ = ['BYOConfigError']
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class BYOConfigError(ValueError):
|
|
6
|
+
"""Subclass of ValueError with the following additional properties:
|
|
7
|
+
args:
|
|
8
|
+
msg: The unformatted error message
|
|
9
|
+
instance_name: The name of which VariableSource instance raised the error
|
|
10
|
+
"""
|
|
11
|
+
def __init__(self, msg, instance):
|
|
12
|
+
errmsg = f"{msg} in VariableSource instance '{instance.var_source_name}'"
|
|
13
|
+
super().__init__(self, errmsg)
|
|
14
|
+
self.msg = msg
|
|
15
|
+
self.instance = instance.var_source_name
|
|
16
|
+
|
|
17
|
+
def __reduce__(self):
|
|
18
|
+
return self.__class__, (self.msg, self.instance)
|
|
19
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: byoconfig
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A configuration class that supports plugins, multiple file formats, heirarchical configuration, and more.
|
|
5
|
+
Author-email: Cam Ratchford <camratchford@gmail.com>
|
|
6
|
+
Classifier: Development Status :: 3 - Alpha
|
|
7
|
+
Classifier: Programming Language :: Python
|
|
8
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Intended Audience :: Information Technology
|
|
11
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
12
|
+
Classifier: Environment :: Win32 (MS Windows)
|
|
13
|
+
Classifier: Environment :: MacOS X
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
|
+
Requires-Python: >=3.8
|
|
16
|
+
Requires-Dist: PyYAML==6.0.2
|
|
17
|
+
Requires-Dist: toml==0.10.2
|
|
18
|
+
Provides-Extra: development
|
|
19
|
+
Requires-Dist: mkdocs; extra == "development"
|
|
20
|
+
Requires-Dist: mkdocs-material; extra == "development"
|
|
21
|
+
Requires-Dist: ruff; extra == "development"
|
|
22
|
+
Requires-Dist: pytest; extra == "development"
|
|
23
|
+
Requires-Dist: build; extra == "development"
|
|
24
|
+
Requires-Dist: twine; extra == "development"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
byo_config/__init__.py
|
|
4
|
+
byo_config/config.py
|
|
5
|
+
byo_config/error.py
|
|
6
|
+
byoconfig.egg-info/PKG-INFO
|
|
7
|
+
byoconfig.egg-info/SOURCES.txt
|
|
8
|
+
byoconfig.egg-info/dependency_links.txt
|
|
9
|
+
byoconfig.egg-info/requires.txt
|
|
10
|
+
byoconfig.egg-info/top_level.txt
|
|
11
|
+
tests/test_config.py
|
|
12
|
+
tests/test_error.py
|
|
13
|
+
tests/test_sources_base.py
|
|
14
|
+
tests/test_sources_env.py
|
|
15
|
+
tests/test_sources_file.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
byo_config
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools", "setuptools-scm"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[tool.setuptools]
|
|
6
|
+
packages = ["byo_config"]
|
|
7
|
+
|
|
8
|
+
[project]
|
|
9
|
+
name = 'byoconfig'
|
|
10
|
+
description = 'A configuration class that supports plugins, multiple file formats, heirarchical configuration, and more.'
|
|
11
|
+
authors = [
|
|
12
|
+
{name = 'Cam Ratchford', email = 'camratchford@gmail.com'},
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
classifiers = [
|
|
16
|
+
'Development Status :: 3 - Alpha',
|
|
17
|
+
'Programming Language :: Python',
|
|
18
|
+
'Programming Language :: Python :: 3 :: Only',
|
|
19
|
+
'Intended Audience :: Developers',
|
|
20
|
+
'Intended Audience :: Information Technology',
|
|
21
|
+
'Operating System :: POSIX :: Linux',
|
|
22
|
+
'Environment :: Win32 (MS Windows)',
|
|
23
|
+
'Environment :: MacOS X',
|
|
24
|
+
'Topic :: Software Development :: Libraries :: Python Modules',
|
|
25
|
+
]
|
|
26
|
+
version = "0.0.1"
|
|
27
|
+
requires-python = '>=3.8'
|
|
28
|
+
dependencies = [
|
|
29
|
+
"PyYAML==6.0.2",
|
|
30
|
+
"toml==0.10.2",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.optional-dependencies]
|
|
34
|
+
development = [
|
|
35
|
+
"mkdocs",
|
|
36
|
+
"mkdocs-material",
|
|
37
|
+
"ruff",
|
|
38
|
+
"pytest",
|
|
39
|
+
"build",
|
|
40
|
+
"twine"
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[tool.ruff]
|
|
44
|
+
exclude = [
|
|
45
|
+
"tests/fixtures/**/*"
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
[tool.ruff.lint]
|
|
49
|
+
select = ["E4", "E7", "E9", "F"]
|
|
50
|
+
ignore = []
|
|
51
|
+
fixable = ["ALL"]
|
|
52
|
+
unfixable = []
|
|
53
|
+
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
|
54
|
+
|
|
55
|
+
[tool.ruff.format]
|
|
56
|
+
quote-style = "double"
|
|
57
|
+
indent-style = "space"
|
|
58
|
+
skip-magic-trailing-comma = false
|
|
59
|
+
line-ending = "auto"
|
|
60
|
+
docstring-code-format = false
|
|
61
|
+
docstring-code-line-length = "dynamic"
|
|
62
|
+
|
|
63
|
+
[tool.pytest.ini_options]
|
|
64
|
+
addopts = "-ra -q"
|
|
65
|
+
testpaths = [
|
|
66
|
+
"tests",
|
|
67
|
+
]
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from os import environ
|
|
2
|
+
from fixtures.pathing import example_configs
|
|
3
|
+
from fixtures.fixture_source_classes import PluginVarSource
|
|
4
|
+
|
|
5
|
+
from byo_config.config import Config
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_file_var_source_functionality():
|
|
9
|
+
example_dict = {
|
|
10
|
+
"parent": {
|
|
11
|
+
"some": "thing",
|
|
12
|
+
"child": {
|
|
13
|
+
"other": "thing"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
yaml_file = str(example_configs / 'same_as.yaml')
|
|
19
|
+
yaml_source = Config(yaml_file)
|
|
20
|
+
assert yaml_source.data == example_dict
|
|
21
|
+
|
|
22
|
+
toml_file = str(example_configs / 'same_as.toml')
|
|
23
|
+
toml_source = Config(toml_file)
|
|
24
|
+
assert toml_source.data == example_dict
|
|
25
|
+
|
|
26
|
+
json_file = str(example_configs / 'same_as.json')
|
|
27
|
+
json_source = Config(json_file)
|
|
28
|
+
assert json_source.data == example_dict
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_env_var_source_functionality():
|
|
32
|
+
env_prefix = 'BYO_CONFIG_TEST_'
|
|
33
|
+
env_var = 'BYO_CONFIG_TEST_ENV_VAR'
|
|
34
|
+
env_val = 'test_value'
|
|
35
|
+
|
|
36
|
+
env_dict = {env_var: env_val}
|
|
37
|
+
environ.update(env_dict)
|
|
38
|
+
|
|
39
|
+
env_source = Config(env_prefix=env_prefix)
|
|
40
|
+
assert env_source.data.get("ENV_VAR") == environ.get(env_var)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_loading_plugins_and_kwargs():
|
|
44
|
+
|
|
45
|
+
config = Config(
|
|
46
|
+
precedence=0,
|
|
47
|
+
test_var1='will_be_overwritten',
|
|
48
|
+
test_var2='will_be_overwritten',
|
|
49
|
+
test_var3='unique to config'
|
|
50
|
+
)
|
|
51
|
+
kwarg_str = 'proof that we can pass plugins kwargs'
|
|
52
|
+
config.include(PluginVarSource, plugin_kwarg=kwarg_str)
|
|
53
|
+
assert config.data['test_var1'] == 'from plugin #1'
|
|
54
|
+
assert config.data['test_var2'] == 'from plugin #2'
|
|
55
|
+
assert config.data['test_var3'] == 'unique to config'
|
|
56
|
+
assert config.data['plugin_kwarg'] == kwarg_str
|
|
File without changes
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
from fixtures.fixture_source_classes import NameSource
|
|
5
|
+
from byo_config.error import BYOConfigError
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_base_source():
|
|
9
|
+
source = NameSource('test')
|
|
10
|
+
assert source.data == {'name': 'test', 'should_appear': 'should_appear'}
|
|
11
|
+
assert source.var_source_name == 'NameSource'
|
|
12
|
+
assert source.precedence == 1
|
|
13
|
+
assert str(source) == 'NameSource: NameSource [1]'
|
|
14
|
+
assert repr(source) == 'NameSource: NameSource [1]'
|
|
15
|
+
source_keys = [k for k in source.data.keys()]
|
|
16
|
+
assert 'precedence' not in source_keys
|
|
17
|
+
assert 'var_source_name' not in source_keys
|
|
18
|
+
assert 'metadata' not in source_keys
|
|
19
|
+
assert '_should_not_appear' not in source_keys
|
|
20
|
+
assert 'should_appear' in source_keys
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_precedence_and_add():
|
|
24
|
+
source_a = NameSource('a', "source_a", 1)
|
|
25
|
+
source_b = NameSource('b', "source_b", 2)
|
|
26
|
+
source_c = NameSource('c', "source_c", 1)
|
|
27
|
+
source_d = NameSource('d', "source_d", 2)
|
|
28
|
+
|
|
29
|
+
# Test __add__ method
|
|
30
|
+
source_a = source_a + source_b
|
|
31
|
+
assert source_a.data == {'name': 'b', 'should_appear': 'should_appear'}
|
|
32
|
+
|
|
33
|
+
# Test all other magic methods that use precedence
|
|
34
|
+
assert int(source_a) == 1
|
|
35
|
+
assert source_a < source_b
|
|
36
|
+
assert source_a <= source_b
|
|
37
|
+
assert source_a <= source_c
|
|
38
|
+
assert source_b > source_a
|
|
39
|
+
assert source_b >= source_a
|
|
40
|
+
assert source_b >= source_d
|
|
41
|
+
assert source_a != source_b
|
|
42
|
+
assert source_a == source_c
|
|
43
|
+
|
|
44
|
+
with pytest.raises(BYOConfigError) as exec_info_precedence:
|
|
45
|
+
source_a += source_c
|
|
46
|
+
assert "as they have the same precedence." in str(exec_info_precedence.value)
|
|
47
|
+
|
|
48
|
+
source_c.precedence = None
|
|
49
|
+
with pytest.raises(BYOConfigError) as exec_info_no_precedence:
|
|
50
|
+
source_a += source_c
|
|
51
|
+
assert "as one or both instances have no precedence value." in str(exec_info_no_precedence.value)
|
|
52
|
+
|
|
53
|
+
# Test that private attributes are not copied over
|
|
54
|
+
source_b._should_not_appear = "shouldn't be copied to other source"
|
|
55
|
+
source_a += source_b
|
|
56
|
+
assert source_a.data == {'name': 'b', 'should_appear': 'should_appear'}
|
|
57
|
+
assert source_a._should_not_appear != source_b._should_not_appear
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_name_conflict():
|
|
61
|
+
source_a = NameSource('a', "source_a", 1)
|
|
62
|
+
source_b = NameSource('b', "source_a", 2)
|
|
63
|
+
with pytest.raises(BYOConfigError) as exec_info_name:
|
|
64
|
+
source_a += source_b
|
|
65
|
+
assert "as they share the same name." in str(exec_info_name.value)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_set_data():
|
|
69
|
+
source_a = NameSource('a', "source_a", 1)
|
|
70
|
+
source_a.data = {'name': 'b'}
|
|
71
|
+
assert source_a.data == {'name': 'b', 'should_appear': 'should_appear'}
|
|
72
|
+
|
|
73
|
+
# Source should be unchanged if data is set to None
|
|
74
|
+
source_a.data = None
|
|
75
|
+
assert source_a.data == {'name': 'b', 'should_appear': 'should_appear'}
|
|
76
|
+
|
|
77
|
+
# Source should be unchanged if data is set to an empty dict
|
|
78
|
+
source_a.data = {}
|
|
79
|
+
assert source_a.data == {'name': 'b', 'should_appear': 'should_appear'}
|
|
80
|
+
|
|
81
|
+
# Test clear_data method with no args
|
|
82
|
+
source_a.clear_data()
|
|
83
|
+
assert source_a.data == {}
|
|
84
|
+
|
|
85
|
+
# Test clear_data method with args
|
|
86
|
+
source_a.data = {'name': 'b', 'age': 30}
|
|
87
|
+
source_a.clear_data('name', 'should_appear')
|
|
88
|
+
assert source_a.data == {'age': 30}
|
|
89
|
+
|
|
90
|
+
# Test clear_data method with non-hashable args
|
|
91
|
+
with pytest.raises(BYOConfigError) as exec_info:
|
|
92
|
+
source_a.clear_data(['name'])
|
|
93
|
+
assert "not hashable types." in str(exec_info.value)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import platform
|
|
2
|
+
from os import environ
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from byo_config.error import BYOConfigError
|
|
7
|
+
|
|
8
|
+
from fixtures.fixture_source_classes import GenericEnvSource
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_error_modes():
|
|
12
|
+
with pytest.raises(BYOConfigError) as exec_info_error:
|
|
13
|
+
GenericEnvSource(111)
|
|
14
|
+
assert "env_prefix must be a string" in str(exec_info_error.value)
|
|
15
|
+
|
|
16
|
+
with pytest.raises(BYOConfigError) as exec_info_error:
|
|
17
|
+
GenericEnvSource("illegal prefix")
|
|
18
|
+
assert "env_prefix must be a valid environment variable name" in str(exec_info_error.value)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def load_env():
|
|
22
|
+
environ.update({
|
|
23
|
+
"BYO_CONFIG_TEST_VAR1": "value1",
|
|
24
|
+
"BYO_CONFIG_TEST_VAR2": "value2",
|
|
25
|
+
"BYO_CONFIG_TEST_var3": "value3", # The case should be ignored on windows, converted to uppercase
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_load_env():
|
|
30
|
+
|
|
31
|
+
prefix = "BYO_CONFIG_TEST_"
|
|
32
|
+
load_env()
|
|
33
|
+
env_source_1 = GenericEnvSource(prefix)
|
|
34
|
+
# Test that it accepts either _ or no _ at the end of the prefix
|
|
35
|
+
env_source_2 = GenericEnvSource(prefix[:-1])
|
|
36
|
+
env_data = {
|
|
37
|
+
"VAR1": "value1",
|
|
38
|
+
"VAR2": "value2",
|
|
39
|
+
"var3": "value3",
|
|
40
|
+
}
|
|
41
|
+
# Handle windows env var case insensitivity
|
|
42
|
+
if platform.system() == "Windows":
|
|
43
|
+
del env_data["var3"]
|
|
44
|
+
env_data["VAR3"] = "value3"
|
|
45
|
+
|
|
46
|
+
assert env_source_1.data == env_data
|
|
47
|
+
assert env_source_2.data == env_data
|
|
48
|
+
|
|
49
|
+
# Use the wildcard to load all environment variables
|
|
50
|
+
env_source_3 = GenericEnvSource("*")
|
|
51
|
+
# Due to slight difference between the two dictionaries, we will compare a subset of values
|
|
52
|
+
assert env_source_3.data['COLUMNS'] == environ.get('COLUMNS')
|
|
53
|
+
if platform.system() == "Windows":
|
|
54
|
+
assert env_source_3.data['HOMEDRIVE'] == environ.get('HOMEDRIVE')
|
|
55
|
+
assert env_source_3.data['HOMEPATH'] == environ.get('HOMEPATH')
|
|
56
|
+
else:
|
|
57
|
+
assert env_source_3.data['HOME'] == environ.get('HOME')
|
|
58
|
+
assert env_source_3.data['USER'] == environ.get('USER')
|
|
59
|
+
|
|
60
|
+
# Test if using None as the prefix does not result in errors
|
|
61
|
+
env_source_4 = GenericEnvSource(None)
|
|
62
|
+
assert env_source_4.data == {}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from byo_config.error import BYOConfigError
|
|
4
|
+
|
|
5
|
+
from fixtures.fixture_source_classes import GenericFileSource
|
|
6
|
+
from fixtures.pathing import example_configs, fixtures_dir, output_dir
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_error_modes():
|
|
10
|
+
# Test that the error modes are working as expected
|
|
11
|
+
with pytest.raises(FileNotFoundError) as exec_info_file_not_found:
|
|
12
|
+
GenericFileSource('non_existent_file', forced_file_type=None)
|
|
13
|
+
assert "Config file non_existent_file does not exist" in str(exec_info_file_not_found.value)
|
|
14
|
+
|
|
15
|
+
python_file = str(fixtures_dir / 'fixture_source_classes.py')
|
|
16
|
+
with pytest.raises(BYOConfigError) as exec_info_error:
|
|
17
|
+
GenericFileSource(python_file, forced_file_type=None)
|
|
18
|
+
assert "does not posses one of the allowed file extensions" in str(exec_info_error.value)
|
|
19
|
+
|
|
20
|
+
invalid_json = str(fixtures_dir / 'invalid.json')
|
|
21
|
+
with pytest.raises(BYOConfigError) as exec_info_error:
|
|
22
|
+
GenericFileSource(invalid_json, forced_file_type=None)
|
|
23
|
+
assert "Expecting property name enclosed in double quotes " in str(exec_info_error.value)
|
|
24
|
+
|
|
25
|
+
invalid_toml = str(fixtures_dir / 'invalid.toml')
|
|
26
|
+
with pytest.raises(BYOConfigError) as exec_info_error:
|
|
27
|
+
GenericFileSource(invalid_toml, forced_file_type=None)
|
|
28
|
+
assert "Invalid group name '{this is invalid}'." in str(exec_info_error.value)
|
|
29
|
+
|
|
30
|
+
invalid_yaml = str(fixtures_dir / 'invalid.yaml')
|
|
31
|
+
with pytest.raises(BYOConfigError) as exec_info_error:
|
|
32
|
+
GenericFileSource(invalid_yaml, forced_file_type=None)
|
|
33
|
+
# todo: figure out how to get yaml error message
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_load_file_modes():
|
|
37
|
+
example_dict = {
|
|
38
|
+
"parent": {
|
|
39
|
+
"some": "thing",
|
|
40
|
+
"child": {
|
|
41
|
+
"other": "thing"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
yaml_file = str(example_configs / 'same_as.yaml')
|
|
47
|
+
yaml_source = GenericFileSource(yaml_file)
|
|
48
|
+
assert yaml_source.data == example_dict
|
|
49
|
+
|
|
50
|
+
toml_file = str(example_configs / 'same_as.toml')
|
|
51
|
+
toml_source = GenericFileSource(toml_file)
|
|
52
|
+
assert toml_source.data == example_dict
|
|
53
|
+
|
|
54
|
+
json_file = str(example_configs / 'same_as.json')
|
|
55
|
+
json_source = GenericFileSource(json_file)
|
|
56
|
+
assert json_source.data == example_dict
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def compare_file_contents(file1, file2):
|
|
60
|
+
with open(file1, 'r') as f1:
|
|
61
|
+
with open(file2, 'r') as f2:
|
|
62
|
+
return f1.read() == f2.read()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_dump_file_modes():
|
|
66
|
+
example_dict = {
|
|
67
|
+
"parent": {
|
|
68
|
+
"some": "thing",
|
|
69
|
+
"child": {
|
|
70
|
+
"other": "thing"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
# ensure that output_dir exists
|
|
75
|
+
output_dir.mkdir(exist_ok=True)
|
|
76
|
+
yaml_file = str(example_configs / 'same_as.yaml')
|
|
77
|
+
yaml_dump = str(output_dir / 'dumped.yaml')
|
|
78
|
+
yaml_source = GenericFileSource(yaml_file)
|
|
79
|
+
yaml_source.dump(yaml_dump)
|
|
80
|
+
assert compare_file_contents(yaml_file, yaml_dump)
|
|
81
|
+
|
|
82
|
+
toml_file = str(example_configs / 'same_as.toml')
|
|
83
|
+
toml_dump = str(output_dir / 'dumped.toml')
|
|
84
|
+
toml_source = GenericFileSource(toml_file)
|
|
85
|
+
toml_source.dump(toml_dump)
|
|
86
|
+
assert compare_file_contents(toml_file, toml_dump)
|
|
87
|
+
|
|
88
|
+
json_file = str(example_configs / 'same_as.json')
|
|
89
|
+
json_dump = str(output_dir / 'dumped.json')
|
|
90
|
+
json_source = GenericFileSource(json_file)
|
|
91
|
+
json_source.dump(json_dump)
|
|
92
|
+
assert compare_file_contents(json_file, json_dump)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_forced_file_type():
|
|
96
|
+
yaml_file = str(fixtures_dir / 'this_is_yaml')
|
|
97
|
+
yaml_source = GenericFileSource(yaml_file, forced_file_type='YAML')
|
|
98
|
+
assert yaml_source.data == {
|
|
99
|
+
'this': 'is yaml'
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
toml_file = str(fixtures_dir / 'this_is_toml')
|
|
103
|
+
toml_source = GenericFileSource(toml_file, forced_file_type='TOML')
|
|
104
|
+
assert toml_source.data == {
|
|
105
|
+
'parent': {'this': 'is toml'}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
json_file = str(fixtures_dir / 'this_is_json')
|
|
109
|
+
json_source = GenericFileSource(json_file, forced_file_type='JSON')
|
|
110
|
+
assert json_source.data == {
|
|
111
|
+
'this': 'is json'
|
|
112
|
+
}
|