1
Fork 0
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:
Steffo 2022-04-14 03:04:38 +02:00
parent 106b27e730
commit db63bf5ddd
Signed by: steffo
GPG key ID: 6965406171929D01
8 changed files with 118 additions and 14 deletions

View file

@ -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>

View 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="&quot;&quot;" />
<option name="_new_parameters" value="&quot;&quot;" />
<option name="_new_additionalArguments" value="&quot;&quot;" />
<option name="_new_target" value="&quot;cfig&quot;" />
<option name="_new_targetType" value="&quot;PYTHON&quot;" />
<method v="2" />
</configuration>
</component>

View file

@ -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",
)

View file

@ -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...") log.debug("Running user-defined configurable function...")
val = f(val) 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",
)

View file

@ -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",
) )

View file

@ -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",
)

View file

@ -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
View 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