mirror of
https://github.com/Steffo99/cfig.git
synced 2024-11-21 15:34:20 +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">
|
||||
<profile version="1.0">
|
||||
<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">
|
||||
<option name="ignoredErrors">
|
||||
<list>
|
||||
<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>
|
||||
</option>
|
||||
</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
|
||||
|
||||
|
||||
__all__ = (
|
||||
"Configurable",
|
||||
)
|
||||
from .config import *
|
||||
from .sources import *
|
||||
from .errors import *
|
||||
from .customtyping import *
|
||||
|
|
|
@ -56,6 +56,11 @@ class Configuration:
|
|||
: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!")
|
||||
|
||||
# noinspection PyMethodMayBeStatic
|
||||
|
@ -85,7 +90,7 @@ class Configuration:
|
|||
log.debug("Item created successfully!")
|
||||
|
||||
log.debug("Registering item in the configuration...")
|
||||
self._register_item(key, item)
|
||||
self._register_item(key, item, configurable.__doc__)
|
||||
log.debug("Registered successfully!")
|
||||
|
||||
# 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("Registering item in the configuration...")
|
||||
self._register_item(key, item)
|
||||
self._register_item(key, item, configurable.__doc__)
|
||||
log.debug("Registered successfully!")
|
||||
|
||||
# Return the created item so it will take the place of the decorated function
|
||||
|
@ -125,12 +130,15 @@ class Configuration:
|
|||
def _decorated():
|
||||
log.debug(f"Retrieving value with key: {key!r}")
|
||||
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}")
|
||||
|
||||
return val
|
||||
|
||||
return _decorated
|
||||
|
@ -176,19 +184,27 @@ class Configuration:
|
|||
if value := self._retrieve_value_optional(key):
|
||||
return value
|
||||
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.
|
||||
"""
|
||||
|
||||
if key in self.items:
|
||||
raise errors.DuplicateError(key)
|
||||
if key in self.docs:
|
||||
raise errors.DuplicateError(key)
|
||||
|
||||
self.items[key] = item
|
||||
self.docs[key] = doc
|
||||
|
||||
def fetch_all(self):
|
||||
log.debug("Fetching now all configuration items...")
|
||||
for value in self.items.values():
|
||||
_ = value.__wrapped__
|
||||
|
||||
|
||||
__all__ = (
|
||||
"Configuration",
|
||||
)
|
||||
|
|
|
@ -5,6 +5,7 @@ TYPE = t.TypeVar("TYPE")
|
|||
|
||||
class Configurable(t.Protocol):
|
||||
__name__: str
|
||||
__doc__: str
|
||||
|
||||
def __call__(self, val: t.Any) -> TYPE:
|
||||
...
|
||||
|
@ -22,6 +23,7 @@ class ConfigurableOptional(Configurable):
|
|||
|
||||
__all__ = (
|
||||
"TYPE",
|
||||
"Configurable",
|
||||
"ConfigurableRequired",
|
||||
"ConfigurableOptional",
|
||||
)
|
||||
|
|
|
@ -28,3 +28,19 @@ class ConfigurationError(Exception):
|
|||
"""
|
||||
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]:
|
||||
path = super().get(key)
|
||||
if path is None:
|
||||
return None
|
||||
try:
|
||||
with open(path, "r") as file:
|
||||
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