mirror of
https://github.com/Steffo99/cfig.git
synced 2024-11-21 23:44:21 +00:00
💥 Write some tests and improve some things
This commit is contained in:
parent
106b27e730
commit
db63bf5ddd
8 changed files with 118 additions and 14 deletions
|
@ -1,10 +1,19 @@
|
||||||
<component name="InspectionProjectProfileManager">
|
<component name="InspectionProjectProfileManager">
|
||||||
<profile version="1.0">
|
<profile version="1.0">
|
||||||
<option name="myName" value="Project Default" />
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="PyComparisonWithNoneInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="PyPep8Inspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
<inspection_tool class="PyPep8Inspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||||
<option name="ignoredErrors">
|
<option name="ignoredErrors">
|
||||||
<list>
|
<list>
|
||||||
<option value="E306" />
|
<option value="E306" />
|
||||||
|
<option value="E711" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PyPep8NamingInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoredErrors">
|
||||||
|
<list>
|
||||||
|
<option value="N802" />
|
||||||
</list>
|
</list>
|
||||||
</option>
|
</option>
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
|
|
18
.idea/runConfigurations/pytest.xml
Normal file
18
.idea/runConfigurations/pytest.xml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="pytest" type="tests" factoryName="py.test">
|
||||||
|
<module name="cfig" />
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="PARENT_ENVS" value="true" />
|
||||||
|
<option name="SDK_HOME" value="$PROJECT_DIR$/.venv/bin/python" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||||
|
<option name="IS_MODULE_SDK" value="true" />
|
||||||
|
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||||
|
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||||
|
<option name="_new_keywords" value="""" />
|
||||||
|
<option name="_new_parameters" value="""" />
|
||||||
|
<option name="_new_additionalArguments" value="""" />
|
||||||
|
<option name="_new_target" value=""cfig"" />
|
||||||
|
<option name="_new_targetType" value=""PYTHON"" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
|
@ -61,9 +61,7 @@ Configuration files of dependencies can be merged into the current
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
from .config import Configurable
|
from .config import *
|
||||||
|
from .sources import *
|
||||||
|
from .errors import *
|
||||||
__all__ = (
|
from .customtyping import *
|
||||||
"Configurable",
|
|
||||||
)
|
|
||||||
|
|
|
@ -56,6 +56,11 @@ class Configuration:
|
||||||
:class:`dict` mapping all keys registered to this object to their respective items.
|
:class:`dict` mapping all keys registered to this object to their respective items.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
self.docs: dict[str, str] = {}
|
||||||
|
"""
|
||||||
|
:class:`dict` mapping all keys registered to this object to the description of what they should contain.
|
||||||
|
"""
|
||||||
|
|
||||||
log.debug("Initialized successfully!")
|
log.debug("Initialized successfully!")
|
||||||
|
|
||||||
# noinspection PyMethodMayBeStatic
|
# noinspection PyMethodMayBeStatic
|
||||||
|
@ -85,7 +90,7 @@ class Configuration:
|
||||||
log.debug("Item created successfully!")
|
log.debug("Item created successfully!")
|
||||||
|
|
||||||
log.debug("Registering item in the configuration...")
|
log.debug("Registering item in the configuration...")
|
||||||
self._register_item(key, item)
|
self._register_item(key, item, configurable.__doc__)
|
||||||
log.debug("Registered successfully!")
|
log.debug("Registered successfully!")
|
||||||
|
|
||||||
# Return the created item so it will take the place of the decorated function
|
# Return the created item so it will take the place of the decorated function
|
||||||
|
@ -108,7 +113,7 @@ class Configuration:
|
||||||
log.debug("Item created successfully!")
|
log.debug("Item created successfully!")
|
||||||
|
|
||||||
log.debug("Registering item in the configuration...")
|
log.debug("Registering item in the configuration...")
|
||||||
self._register_item(key, item)
|
self._register_item(key, item, configurable.__doc__)
|
||||||
log.debug("Registered successfully!")
|
log.debug("Registered successfully!")
|
||||||
|
|
||||||
# Return the created item so it will take the place of the decorated function
|
# Return the created item so it will take the place of the decorated function
|
||||||
|
@ -125,12 +130,15 @@ class Configuration:
|
||||||
def _decorated():
|
def _decorated():
|
||||||
log.debug(f"Retrieving value with key: {key!r}")
|
log.debug(f"Retrieving value with key: {key!r}")
|
||||||
val = self._retrieve_value_optional(key)
|
val = self._retrieve_value_optional(key)
|
||||||
log.debug("Retrieved val successfully!")
|
log.debug("Retrieved value successfully!")
|
||||||
|
|
||||||
|
if val is None:
|
||||||
|
log.debug(f"Not running user-defined configurable function since value is {val!r}.")
|
||||||
|
else:
|
||||||
|
log.debug("Running user-defined configurable function...")
|
||||||
|
val = f(val)
|
||||||
|
|
||||||
log.debug("Running user-defined configurable function...")
|
|
||||||
val = f(val)
|
|
||||||
log.info(f"{key} = {val!r}")
|
log.info(f"{key} = {val!r}")
|
||||||
|
|
||||||
return val
|
return val
|
||||||
|
|
||||||
return _decorated
|
return _decorated
|
||||||
|
@ -176,19 +184,27 @@ class Configuration:
|
||||||
if value := self._retrieve_value_optional(key):
|
if value := self._retrieve_value_optional(key):
|
||||||
return value
|
return value
|
||||||
else:
|
else:
|
||||||
raise errors.ConfigurationError(f"Missing {key} configuration value.")
|
raise errors.MissingValueError(key)
|
||||||
|
|
||||||
def _register_item(self, key, item):
|
def _register_item(self, key, item, doc):
|
||||||
"""
|
"""
|
||||||
Register an item in this Configuration.
|
Register an item in this Configuration.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if key in self.items:
|
if key in self.items:
|
||||||
raise errors.DuplicateError(key)
|
raise errors.DuplicateError(key)
|
||||||
|
if key in self.docs:
|
||||||
|
raise errors.DuplicateError(key)
|
||||||
|
|
||||||
self.items[key] = item
|
self.items[key] = item
|
||||||
|
self.docs[key] = doc
|
||||||
|
|
||||||
def fetch_all(self):
|
def fetch_all(self):
|
||||||
log.debug("Fetching now all configuration items...")
|
log.debug("Fetching now all configuration items...")
|
||||||
for value in self.items.values():
|
for value in self.items.values():
|
||||||
_ = value.__wrapped__
|
_ = value.__wrapped__
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
"Configuration",
|
||||||
|
)
|
||||||
|
|
|
@ -5,6 +5,7 @@ TYPE = t.TypeVar("TYPE")
|
||||||
|
|
||||||
class Configurable(t.Protocol):
|
class Configurable(t.Protocol):
|
||||||
__name__: str
|
__name__: str
|
||||||
|
__doc__: str
|
||||||
|
|
||||||
def __call__(self, val: t.Any) -> TYPE:
|
def __call__(self, val: t.Any) -> TYPE:
|
||||||
...
|
...
|
||||||
|
@ -22,6 +23,7 @@ class ConfigurableOptional(Configurable):
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
"TYPE",
|
"TYPE",
|
||||||
|
"Configurable",
|
||||||
"ConfigurableRequired",
|
"ConfigurableRequired",
|
||||||
"ConfigurableOptional",
|
"ConfigurableOptional",
|
||||||
)
|
)
|
||||||
|
|
|
@ -28,3 +28,19 @@ class ConfigurationError(Exception):
|
||||||
"""
|
"""
|
||||||
An error is present in the configuration specified by the user.
|
An error is present in the configuration specified by the user.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class MissingValueError(ConfigurationError):
|
||||||
|
"""
|
||||||
|
A required configuration key has no value.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
"DefinitionError",
|
||||||
|
"UnknownKeyError",
|
||||||
|
"RegistrationError",
|
||||||
|
"DuplicateError",
|
||||||
|
"ConfigurationError",
|
||||||
|
"MissingValueError",
|
||||||
|
)
|
||||||
|
|
|
@ -58,6 +58,8 @@ class EnvironmentFileSource(EnvironmentSource):
|
||||||
|
|
||||||
def get(self, key: str) -> t.Optional[str]:
|
def get(self, key: str) -> t.Optional[str]:
|
||||||
path = super().get(key)
|
path = super().get(key)
|
||||||
|
if path is None:
|
||||||
|
return None
|
||||||
try:
|
try:
|
||||||
with open(path, "r") as file:
|
with open(path, "r") as file:
|
||||||
return file.read()
|
return file.read()
|
||||||
|
|
43
cfig/tests/test_all.py
Normal file
43
cfig/tests/test_all.py
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import pytest
|
||||||
|
import cfig
|
||||||
|
|
||||||
|
|
||||||
|
def test_config(monkeypatch):
|
||||||
|
config = cfig.Configuration()
|
||||||
|
|
||||||
|
@config.required()
|
||||||
|
def FIRST_NUMBER(val: str) -> int:
|
||||||
|
"""The first number to sum."""
|
||||||
|
return int(val)
|
||||||
|
|
||||||
|
@config.optional()
|
||||||
|
def SECOND_NUMBER(val: str) -> int:
|
||||||
|
"""The second number to sum."""
|
||||||
|
return int(val)
|
||||||
|
|
||||||
|
# Assert the two configuration items have been registered
|
||||||
|
assert "FIRST_NUMBER" in config.items
|
||||||
|
assert "FIRST_NUMBER" in config.docs
|
||||||
|
assert "SECOND_NUMBER" in config.items
|
||||||
|
assert "SECOND_NUMBER" in config.docs
|
||||||
|
|
||||||
|
# Assert docstrings are accessible even if the two items aren't
|
||||||
|
assert config.docs["FIRST_NUMBER"] == """The first number to sum."""
|
||||||
|
assert config.docs["SECOND_NUMBER"] == """The second number to sum."""
|
||||||
|
|
||||||
|
# Assert that an error is raised if items are fetched without any value set
|
||||||
|
with pytest.raises(cfig.MissingValueError):
|
||||||
|
config.fetch_all()
|
||||||
|
|
||||||
|
# Setup the environment
|
||||||
|
monkeypatch.setenv("FIRST_NUMBER", "1")
|
||||||
|
|
||||||
|
# Assert that no error is raised with all required values set
|
||||||
|
config.fetch_all()
|
||||||
|
|
||||||
|
# Assert the two variables have the correct values
|
||||||
|
assert FIRST_NUMBER == 1
|
||||||
|
assert SECOND_NUMBER == None
|
||||||
|
|
||||||
|
# Please note that SECOND_NUMBER is not the same instance as None, as it is a lazy object proxy!
|
||||||
|
assert SECOND_NUMBER is not None
|
Loading…
Reference in a new issue